前情提要:本人非研发,该文章仅讨论网络通信流程,以访问京东为例。

首先,咖啡。😉

众所周知,访问网页的域名得先知道域名对应的ip地址,该流程后续看心情我会出一期。

在你已经获得域名对应的IP地址想要进行应用层通信之后,我们伟大的研发定义要做的第一件事,就是TCP三次握手

TCP三次握手

TCP三次握手也是老生常谈的了,SYN,SYN ACK,ACK,我们一个个来看。

因为我们需要访问的是网页,同时它是一个安全的网页,那么数据包的目的端口即443,源端口默认是随机TCP高端口,这个不再多说。

SYN

作为TCP三次握手的第一个报文,咱们必须给它扒皮抽筋,里里外外的看一遍。

  • Flags位:会把Syn置位为1,代表这是一个SYN报文。

  • Windows Size:默认65535.

  • 初始序列号:随机定义,用于后续的计算,此处假设它为867986412,给它一个代号,x

Option

TCP会携带Option字段,咱们一个个看

  • Mss:windows默认是1460,也就是1500的mtu减去20字节IP报头+20字节的TCP报头。

  • Windows scale:8,该字段称为窗口缩放,主要的作用是扩展TCP的窗口大小,原本用于表示TCP窗口大小默认只有16位,也就是最大65535,在目前高速发展的网络环境中是肯定不够用的,这个字段客户端和服务器会进行协商。实际的窗口大小会按照TCP头部中的窗口值*2^Windows scale,在这是2的8次幂,也就是256。计算后得到TCP的接收窗口大小可以达到16776960字节,也就是大约16M.

  • Sack Permit:该字段用于在TCP连接建立时协商启用选择性确认功能。启用Sack后,接收方在网络发生丢包之后,可以在ACK中告知对方:我接收到了哪些数据,而不仅仅只是序列号,这样发送方只需要重传丢失的数据,而不是从丢包位置开始的所有数据,提高传输效率和性能。

抓包发现咱们伟大的研发为了保证效率,会直接重传一份SYN报文,以确保哪怕真的一开始就丢包了,还能有一份一模一样的数据发送给服务器,而不是让我们傻等,好评。此时会填写时间戳字段,差不多是1s的间隔。第一份SYN的时间戳为0,为了方便后续计算。

SYN ACK

该报文为TCP三次握手严格意义上的第二个报文,也是服务器的第一份回包。此时服务器会用它的443端口给我们做回包。

  • Flags位:该报文同时置位SYNACK,代表这是一个SYN ACK报文。

  • Windows Size:42340,这是我抓到的服务器给我返回的他的窗口大小。

  • 序列号:随机定义,用于让客户端做确认,此处假设它是1764966876,同时会把ack number设置为客户端发送的SYN报文的序列号+1,也就是前面我写的x的值+1,此处是867986413。

Option

服务器给咱的MSS同样也是1460

Sack permit同样携带

Window scale:9,也就是2的9次幂,代表512。

时间戳会在第二份重传的SYN基础上继续累加,也就是重传的SYN携带的1s,加上收到SYN的时间。

ACK

这是TCP三次握手的最后一个报文,也就是客户端对服务器的确认报文,目的端口是服务器的443端口

  • Flags位:置位ACK,代表这是一个ACK报文

  • Windows Size:255,此处会进行计算,用65535减去前面的256,得出65280.

  • 序列号:填写为服务器给出的SYN ACK的确认号,同时该报文的确认号进行计算,对服务器的序列号+1,也就是1764966877

  • 时间戳:把收到SYN ACK的时间加上SYN ACK本身携带的时间戳

至此,TCP三次握手结束。

TLS握手

通过TCP三次握手建立连接之后,为了实现安全通信,客户端与服务端会进行TLS握手协商。此处以TLS1.3为例。

Client Hello

客户端主动发送该报文,TCP目的端口仍为443,TCP Flags置位 PUSH和ACK,代表这是一个PUSH,ACK报文

在TCP与TLS报头之间会插入一个Reassembles TCP Segments,该报头会描述需要发送的TCP报文大小,以及需要分片成几个包,每个包的大小是多少。假如TCP长度为1720字节,则需要分片的TCP载荷第一个报文为1460字节,第二个为260字节。

  • Content type:告诉对方这是一个Handshake握手包

  • Handshake protocol:设置成Client Hello,表示握手包协议是客户端hello包

  • Version:实际上TLS1.3并不使用这个字段,只是为了向后兼容从而保留,默认设置为TLS 1.2 (0x0303)

  • Random:32字节的随机数,用于后续生成密钥材料,保证唯一性。

  • Session ID长度:32

  • Session id:客户端的会话标识符,用于兼容早期版本

  • Cipher Suites:列出客户端支持的密钥套件。TLS 1.3与1.2的密钥套件不同,此处展示1.3的套件列表

  • Compressoion Methods:仅为了兼容早期版本才保留,默认TLS 1.3只保留“null”压缩方法

然后是扩展字段,该字段包含很多内容,是TLS的核心,其主要核心内容如下:

  • supported_version:该字段明确标识客户端支持的版本,TLS 1.3中该字段会填充TLS 1.3(0x0304)和TLS 1.2(0x0303)

  • server_name:标识客户端要连接的url,此处因为我需要访问京东,会填写京东的域名,也就是www.jd.com,该字段也称为SNI

  • key_share:用于密钥交换,包含客户端选定的椭圆曲线(或其他)参数及对应的公钥。服务器将从中选择一个,完成 ECDHE(椭圆曲线 Diffie-Hellman Ephemeral)密钥交换。

  • psk_key_exchange_modes:指定客户端支持的预共享密钥和密钥交换模式,TLS 1.3 默认支持 “psk_dhe_ke” 模式

在客户端发出这个client hello报文之后,网关会给客户端一个ICMP目的不可达(需要分片)的回包,该ICMP报文载荷内,包含了下一跳设备的MTU值。ICMP内部嵌入了一系列载荷:源为客户端目的为服务端IP地址的IPv4报头,该报头置位了DF位,protocol设置成了TCP;一个TCP报头,目的端口还是443,置位了ACK;外加一个TLS包头。

在这之后,客户端就知道,它自己需要分片了,会发出一个1506字节的TCP Out-Of-Order包,其中TCP载荷为1452字节,剩余的是14字节的二层报头(两个6字节的源目mac+2字节的类型字段),20字节的IP包头,20字节的TCP包头。

服务器接收到之后,会回一个TCP window update报文给到客户端,告诉客户端,服务端还能接收多少字节的数据

之后客户端会继续发送分片的TCP报文,置位了Push和ACK位

等到客户端完全发送完了数据之后,服务器会进行回包

Server Hello

Version:同Client hello一样,设置为TLS 1.2

Random:32位的随机数,参与后续密钥派生

session id:设置为客户端发送的client hello中包含的会话id,用于确认该会话

Cipher Suite:选择了client hello中包含的一个密钥套件,决定双方后续使用的加密算法

在扩展字段中,重要的依旧是支持的版本,也就是明确使用的版本,此处服务端如果支持TLS1.3,则会设置1.3

还有key_share字段,服务端提供它自身的密钥,用于与客户端进行共享密钥计算

后续的信息

在TLS1.3中,对之后的握手交互流程进行了简化,不再需要像TLS 1.2那样交互大量的中间信息。

TLS握手之后,双方就可以交互应用层数据,每个应用层数据都可以分成多个包进行发送,且每一个都可以独立加密认证。

如果试图结束会话,无论是客户端还是服务器端,都会发送FIN,ACK报文,进行会话终止。

文章作者: Wippe
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Wippe's Blog
Network Network
喜欢就支持一下吧