首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

单击链接之后的的网络

当你点击一个超链接时,你的浏览器从远程服务器加载链接的内容并进行渲染。在幕后,涉及连接建立、会话加密、协议协商、重定向、域指示等等许多操作。

在本文中,我将为你详细介绍在浏览器中点击超链接时的网络方面。

在本文中提到的“客户端”指的是支持TLS 1.3和HTTP/2协议的浏览器。在2022年,所有现代浏览器都支持这些协议。

https://slagga.top 点击该链接会将浏览器重定向到我的个人博客,该过程涉及到DNS、TCP、TLS、ALPN/SNI和HTTP/2,我将详细介绍每个部分。

slagga.top

---> gitslagga.github.io

---> 185.199.108.153

DNS

当你点击 https://slagga.top 时,HTTP客户端(你的浏览器)会发起一个DNS查找请求来获取slagga.top的IP地址。slagga.top是一个CNAME记录,托管在 Cloudflare 的DNS权威名称服务器上,它指向一个Github DNS记录gitslagga.github.io,我的HTML文件就托管在这里。

这意味着你可以在浏览器中访问gitslagga.github.io。

DNS查询是一个带有唯一查询ID的UDP数据包,用于获取slagga.top的IP地址。DNS查询的第一站是你的DNS递归器(或解析器),例如Google的8.8.8.8或Cloudflare的1.1.1.1。递归器向ROOT DNS服务器查询.com顶级域名服务器(TLD)。然后递归器向TLD发送一个查询,请求slagga.top托管的权威名称服务器,并返回其中一个enom服务器。最后,递归器将DNS查询slagga.top发送到一个Github服务器以获取IP地址。它发现这是一个指向gitslagga.github.io的CNAME,因此它进行了新的DNS查询,以获取gitslagga.github.io的IP地址,这个过程一直重复,直到发现一个IP地址。这些假设没有使用缓存。

在DNS术语中,DNS查找和DNS解析之间有一些微小的差别。操作系统中的DNS查找意味着它将尝试查找IP地址,这涉及检查本地缓存、主机文件,最后通过网络调用进行解析。DNS解析只是最后的步骤,通过网络进行DNS解析。getaddrinfo是Linux上执行DNS查找的函数,这也是为什么这个函数是同步的。

我们可以通过nslookup(或dig)命令来查看这一点,来查询该域名。

在我们获取到github域的DNS查询中,还得到了与github.io域关联的两个IPv4地址的A记录。客户端将选择一个IP地址并建立与185.199.108.153的TCP连接。

DNS可以配置为返回多个A记录的IP地址。轮询DNS会更改返回的A记录的顺序,以便客户端可以在不同的IP地址上分布连接。这样传统的客户端总是选择第一个IP地址的问题就不会过载该特定服务。当然,可以在客户端层面应用一些技巧,强制实现我所说的客户端负载均衡。

TCP

现在我们有了一个IP地址,我们可以建立TCP连接了。建立TCP连接需要4个元组,源IP、源端口、目标IP和目标端口。客户端在连接之前需要这四个元组。

客户端通过DNS知道了目标IP,即185.199.108.153,目标端口是443,因为链接明确指定为https://。源端口可以是0-2¹⁶之间的任何可用端口,而源IP是您的机器。

虽然源IP通常起始于机器的私有IP地址,但在离开私有网络之前,它会更改为网关的公共IP地址,这个过程称为NAT或网络地址转换。源端口也会发生变化,并在NAT表中添加一个条目来记住变化,以便网关知道如何将数据包转发回原始机器。

通过这四个元组,客户端发送一个携带SYN的TCP段,封装在一个IP数据包中,服务器接收到SYN后回复一个带有SYN/ACK的数据包,并将目标IP更改为客户端的IP(很可能是网关),最后客户端通过ACK完成握手。现在我们建立了一个连接。

从技术上讲,服务器端的连接建立是由运行在服务器上的内核操作系统完成的,即185.199.108.153上的443端口。当后端应用程序侦听一个端口/地址时,会分配两个队列,即SYN队列和ACCEPT队列。SYN队列保存未完成的SYN,直到从客户端返回最后匹配的ACK,创建连接并将其移动到ACCEPT队列。连接在ACCEPT队列中等待,直到后端应用程序调用accept()来创建文件描述符并读取数据。

Client ------------SYN -------> github (185.199.108.153)

CK------

------------ACK-------->

TLS

在当前状态下,在TCP连接上传输的任何数据都是明文的,任何拦截流量的人都可以观察到。这就是为什么通信需要进行加密以确保安全性(HTTPS中的S)。

我们选择的加密协议是传输层安全性(TLS)。TLS的主要部分是握手,其主要目标是:

交换用于加密的对称密钥

协商应用层协议

标识和认证服务器

客户端发送TLS客户端hello消息来启动TLS握手过程,请求会话加密,并在此过程中提议使用HTTP/1.1和HTTP/2。服务器回复一个服务器hello消息以完成TLS握手。

Client ------------SYN -------> Github (185.199.108.153)

CK------

------------ACK-------->

------Client Hello ---->

客户端在其客户端hello消息中设置了许多TLS扩展,我们特别关注其中的两个。第一个是ALPN,即应用层协议协商,第二个是SNI,即服务器名称指示。让我们解释一下它们的目的。

客户端提议了TLS协议版本TLS 1.3和TLS 1.2,但实际上它还不知道服务器将接受哪个协议。如果服务器选择了TLS 1.3,则握手在一次往返中完成;否则,握手需要两次往返。我们假设这里使用的是TLS 1.3,因为我们知道Github使用的是TLS 1.3。

ALPN

ALPN是一个TLS扩展,用于指示客户端支持的应用层协议。在我们的情况下,客户端提议同时支持HTTP/1.1和HTTP/2(简写为h1和h2),通常选择客户端和服务器都支持的最高协议。在这种情况下,服务器选择了HTTP/2。

SNI

TLS握手中最重要的部分可能是SNI(Server Name Indication,服务器名称指示)。该扩展向服务器指示客户端感兴趣的域名。这是因为一个IP地址可以托管数千个网站,指示域名可以帮助服务器准确地知道客户端感兴趣的是哪个网站,从而使服务器知道返回哪个证书。

客户端的SNI设置为slagga.top。Cloudflare服务器接收到TLS客户端hello握手,并使用SNI,它准确地知道需要为客户端提供什么证书进行身份验证。该证书是在将我的域名与Cloudflare关联时由Cloudflare为我的域名生成的,证书颁发机构是Cloudflare。

服务器发送服务器hello以完成TLS握手,其中包含slagga.top的证书、密码参数和首选的应用协议,即HTTP/2。客户端和服务器都具有用于加密的对称密钥,它们准备好加密并发送HTTP请求。

由于我们使用TLS 1.3,所以证书等内容是从服务器开始进行加密的。原因是服务器拥有对称密钥。与TLS 1.2不同,在TLS 1.2中,服务器在服务器hello中以明文发送证书,因为密钥直到第二轮往返之前才创建。我在这篇文章中讨论了这个决策的原因。

HTTP/2

我们在TCP连接上建立了加密会话。现在客户端可以发送HTTP GET请求来获取页面。选择的应用层协议是HTTP/2,因此客户端需要一个HTTP/2流来发送请求。

由于这是一个新的连接,客户端为请求创建了一个新的流(流1)。客户端发送带有GET方法的HTTP请求,路径为/,因为在slagga.top之后没有其他内容,并且协议版本为HTTP/2。客户端设置HTTP头并发送请求。

以下是一个示例,展示了请求的样子。请注意Host头部,这是最重要的HTTP头之一。

HTTP/2中,奇数编号的流是由客户端发起的,而偶数编号的流是由服务器发起的。

gRPC充分利用了HTTP/2协议,这些流对于该协议的功能非常重要。

Host 头部

没有 Host 头部,整个过程就会出现问题。Host 头部最初在 HTTP/1.0 中是可选的,用于解决一些网站管理员面临的挑战。其中一些挑战包括:

在单个 IP 地址上托管多个网站

代理服务器支持

因此,在后来的 HTTP/1.1 和未来的协议中,Host 头部被要求必须存在。

以 GitHub 为例,IP 地址 185.199.108.153 托管了数千个网站。许多客户端连接到相同的 IP 地址,但是 GitHub 服务器如何知道客户端真正想要访问哪个网站呢?服务器使用 Host 头部来确定要获取哪个网站。请注意,客户端将 slagg.top 设置为 Host 头部,以便 GitHub 服务器可以指向我的 GitHub 仓库的内容副本,并从那里提供 HTML 页面。

客户端可能还会在 GET 请求中发送 Cookies 头部,服务器用于识别用户。

在代理配置中,客户端的目标 IP 是代理服务器,因此我们需要在应用层提供另一种指示,以便代理服务器知道客户端实际要连接的是哪个网站。

服务器在流 1 上接收到 GET 请求,并通过查看 Host 头部来处理 GET 请求,获取默认页面 index.html。基于是否发送了 Cookies,服务器将在同一流上返回不同的 HTTP 响应。

值得注意的是,向同一域发送的 HTTP 请求将尽可能使用现有的连接。这是为了避免创建和加密新连接的成本。在 HTTP/2 中支持请求复用,客户端可以在同一连接上的不同流上发送多个并发请求。然而,在 HTTP/1.1 中,一个连接一次只能处理一个请求。

跳转到 Github

最后,客户端收到了包含 index.html 页面的 HTTP 响应。

请注意 meta 头部中的 http-equiv 属性和 refresh、content=0 以及 URL。这告诉浏览器立即重定向到该 URL 上的 Github 网站。步骤如下:

进行 github.io 的 DNS 解析,找到 IP 地址

创建 TCP 连接

建立 TLS 连接

协商 HTTP 协议

创建 HTTP/2 流

发送一个新的 GET 请求,路径为 /index.html

如果您已登录到 Github,会发送 Cookies

总结

本文的目标是揭示软件工程的艺术。伟大的工程师们在设计和实现支撑互联网一切的通信协议方面付出了大量的努力。

作为一名工程师,我感觉这项工作经常被人们视为理所当然,很少被认可。了解协议的工作原理是为网络工程的进化做出贡献的第一步,也有可能创造出更好的协议。您看到了实现点击链接这样一个简单任务所需的步骤,那么问题就是,我们能否做得更好呢?

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230521A030AD00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券