首页 > 代码库 > High Performance Browser Networking - TCP UDP TLS
High Performance Browser Networking - TCP UDP TLS
时延
时延的定义和标准
时延简单的说是从原点到目标点传送一条信息或者一个数据包,所花费的时间。 时延=发送时延+传播时延+处理时延+排队时延:
Propagation delay 传播时延
传播时延这个概念,是指电磁信号或者光信号在传输介质中传输的时延,而在光纤或者铜线中,光信号和电磁信号的传播速度都在20万公里/秒以上,在传输介质中传输的是电磁信号或者光信号,而不是数据!
Transmission delay 传送时延
发送时延是指结点在发送数据时使数据块从结点进入到传输媒体所需的时间,也就是从数据块的第一个比特开始发送算起,到最后一个比特发送完毕所需的时间。发送时延又称为传输时延
Processing delay 处理时延
处理数据包的头部,校对位错误, 并确定数据包要传输的方向
Queuing delay 排队时延
数据包等待处理的时间
总的时延是从客户端到服务器所有时延的总各,Propagation 时间是由两者之间的距离和传播的介质(光纤或铜线)决定。另一方面,Transmission delay 是由两者建立起来传输链接的传输速率决定的,跟两者之者的距离没有关系。 举例来说, 假设我们分别在1Mb的链接和100Mb的链接,传递一个10Mb的文件。 前者的时间为10称,而100Mbps时,只需花费0.1秒。
下一步, 当数据包到达一个路由器, 路由器必需检测数据包的头,从而决定下一个路由, 同时还需要对数据进行校验, 所有的这些逻辑都是在路由器硬件上完成,所以Processing delay是非常小的, 但它也确时存在。 最后来讲讲Queuing delay. 当数据包从较快的传速速率(光钎)到一个100MB的路由器,这就常过了路由器的处理速度, 那么这个数据包就需要进入到缓存区提排队。 这个排队时间,就是Queuing Delay.
光速与Propagation delay
光速是299, 792, 458米每秒, 但光速在介质中传播会有能量损耗, 这只是一个理论的值,通过以下图表,可以清楚的了解光纤实际的速度和造成的Propagation 延时
路径 | 距离 | 真空光速延时 | 光纤延时 | 光纤信号往返时间 |
---|---|---|---|---|
New York to San Francisco | 4,148 km | 14 ms | 21 ms | 42 ms |
New York to London | 5,585 km | 19 ms | 28 ms | 56 ms |
New York to Sydney(悉尼) | 15,993 km | 53 ms | 80 ms | 160 ms |
绕赤道一周 | 40,075 km | 133.7 ms | 200 ms | 200 ms |
光纤上的传输速度是挺快的, 但从纽约到悉尼还是花费了160毫秒. 这也仅仅是理论上的值(两个城市是直接通过光纤相连), 实际上数据包会经过不同的路径(悉尼到旧金山, 在到纽约等等)。经理的路径越多,就会引入更多的处理延时,等待延时, 传输延时。最终RTT(Round-trip time)将达到 200 - 300ms.
通常100-200ms是正常的值, 如果超过了300ms, 那么整个交互就变慢了. 可以使用Content delivery network(CDN), 将服务器的资料缓存在离客户端最近的服务器。 减少两者之者的距离.
构建TCP
互联网由两个重要的协议组成,IP 和 TCP. IP(Internet Protocol) 提供了通信双方之间的路由以及寻址,它只管发送数据,而不保证数据是否完速的传送到了接收端。TCP(Transmission Control Protocal), 提供了在不可靠的通道上,建立一个抽像的可靠网络。
TCP 提供了一个可靠的抽像网络。它向应用程序隐藏了复杂的网络通信细节: 重发丢失的数据, 按序发送,拥塞控制, 数据的完整性等。 当你使用TCP时,你可以保证发送和接受到的数据是一样的,而且顺序也是一样的。 所以TCP是保证准确传输最佳化的协议。
TCP 并不是 HTTP唯一的传送协议, HTTP也可以建立在用户数据报协议(User Datagram Protocal) 或者 UDP, 也可以选择其它传送协议。 但在实际中, HTTP在互联网的通信都是通过 TCP。
所以理解TCP的核心运行原理,是我们优化web性能的基础知识。
三次握手
所有的TCP都是从三次握手开始, 在客户端或者服务器交换任意应用数据之前,它们必须先建立连接,确认数据包的序列号, 以及其它用于连接的特殊变量。 由于安全原因,序列号是从双方中随机挑选。
SYN
客户端会挑选出一个数字序列号 X, 并且发送一个SYN 数据包(包含额外的TCP 标记和选项)
SYN ACK
服务器接受到 syn 包, 在序列号X上加1. 并且挑选一个序列号Y, 并附上它自己的标记和选项, 并响应这个数据包
ACK
客户端对x, y 加1 , 并发回ACK包, 完成握手。
图1-1
当三次握手完成, 应用层数据就可以在客户端和服务器之间传送。 客户端可以在ACK 包发送完后立即传送数据包。 但服务器必须在接受到ACK包之后,才可以。 整个的启动过程(三次握手) , 每个TCP连接都需要进行, 所以对于使用TCP的应用程序, 这个过程会非常重要: 每一个新的连接在数据传输之前都会有一个响应延时。
举一个例子, 如果我们的客户端在New York, 但服务器是在London, TCP连接是建立在光纤上的, 三次握手将至少花费56 毫秒(客户端可以在第三次ACK后,立即传输数据): 28ms 是一个方向的Propogation delay.
三次握手产生的延时,使得建立新的TCP连接是昂贵的, 这也是为什么在使用TCP时,需要对已有连接进行重要最大的原因之一.
Slow-Start 慢启动
比如浏览器(图1-1的第二步, y+1, x+1 后就可以发送数据了, 那么此时浏览器的cwnd 为 1MSS, 在一个RTT时间内,收到带ACKed的数据包后,会增加1个MSS. 可以发送两个数据包了。)
而服务器要在接到收到浏览器的ACK包后,才可以发送数据, 它的cwnd 为1MSS, 在下一次接收到客户端的ACKed包后,会增加1MSS.
方程式 2-1 表式的是, 达到指定的传输速度所需要的时间:
让我们假设以下情下:
- 客户端和服务器的速度达到 65,535 (64KB)
- 初始cwnd: 4 MSS (RFC2581)
- RTT(一个响应用的时间): 56ms (London to New York)
每一个新建的TCP连接的吞吐量都被限制为 cwnd的大小, 实际上, 要达到64 KB的限制, 我们将增加cwnd的大小为45 MSS, 这需要花费244毫秒:
为了减小在增加拥塞空口大小,所花的时间,我们可以减小 服务器与客户端 RTT的时间。 例如, 将服务器布置到离客户端更进的位置。 或者,我们可以增加初始的 cwnd的大小 到 10 MSS (RFC 6928).
对于视频以及大型文件来说, Slow-start不是特别大的问题, 但对于很多短的和突发的http来说, 因为它限制了带宽吞吐量的能力(高带宽,但使用不上). 这就会对性能造成影响。
- $> sysctl net.ipv4.tcp_slow_start_after_idle
- $> sysctl -w net.ipv4.tcp_slow_start_after_idle=0
为了了解三次握手和慢启动对http请求造成的影响,我们假设 New York的客户 从 London的服务器请求一个20 KB的文件。新的TCP连接,如图 2-5所示。
- RTT 的时间为 56ms
- Client 和 Sever 之间的带宽 5 Mbps;
- Client 和 server 的 receive window(rwnd): 65,535 bytes (64KB)
- 初始 cwnd : 4 MSS (4 × 1460 bytes ≈ 5.7 KB)
- 服务器处理响应的时间: 40 ms
- GET请求大小,小于单个 segement( 1460)
图2-5
0 ms Client 通过SYN包,进行TCP第一次握手
28ms 服务器返回一个SYN-ACK, 并且有指定 rwnd 大小
56ms Client 发送一个对SYN-ACK所的确认包, 并指定 自已的 rwnd大小, 并立即发送一个 HTTP GET 请求
84ms 服务器接受 HTTP request, 并花 40 ms处理
124ms 服务器完成,并产生一个20kb的响应, 并且发送4 TCP segments的数据(5480 bytes), 并暂停,等待客户端返回一个 ACK包
152ms 客户端接收到 4 个片段的数据, 并确认每一段数据, 并返回一个ACKed 包。
180ms 服务器增加cwnd的值, 并发送8 个数据段
208ms 客户端接收到 8个数据段,并确认每一段数据
236ms 服务器增加每个ACK 包的 cwnd, 并发送剩余的数据段
264ms 客户端接收剩余数据段,并返回每一个段的ACKs包
在一个新的TCP传递一个20KB的数据, 花费了264ms . 让我们对比一下,重用相同的TCP连接, 并请求相同的内容
图2-6
0 ms 客户端发送请求
28ms 服务器接收到请求
68ms 服务器处理完请求,并生成20KB的响应,但是当前的cwnd为15 segments, 因此可以一次性发送完20KB的数据
96ms 客户端接收到15个数据段,并ACKs 它们
同样相同的请求在相同的连接上(除了三次握手)。 性能上提升了275%;
在这两个例子中, 5 Mbps的带宽没有对性能产生任何影响,主要的影响因素是拥塞窗口大小。
拥塞避免
Bandwidth-Delay Product (带宽迟延乘积)
TCP 内制的拥塞控制和拥塞避免带来了另一个重要的性能优化: 发生者和接收者最佳值,这取决于RTT 和 两者之前的带宽( data rate)
我们回忆一下之前内容, 在发送者和接收者之间传送中的数据大小(unacknowledged),取决于 rwnd 和 cwnd 中最小的一个。 rwnd 的大小每次都会出现在ACK包中(固定), 而 cwnd 依据发送者的拥塞控制和拥塞避免,动态调整的。
如果发生者 或 接收者 超过了最大的unacknowledged data(未确认数据), 它必须停下来,等待其它之前发送过的包的确认信息(ACK). 那么它需要等待多久呢? 这取决于两者之者的响应时间RTT.
Bandwidth-Delay Product
数据链路上带宽与 end-to-end 之间的延时乘积。 它所表示的是, 任一时间点, 可以发送的最大 未确认(unacknowledged)的数据.
sender 或 receiver 在停下来等待之前发送的包确认信息, 就会有一段时间空隙(data gap), 在这段空隙内,发送端是不能在发送数据的。 为了解决这个问题, 窗口的大小就需要调整到足够大, 这样服sender在接受到receiver 返回的ACK包之前,也可以继续发送数据。 这样就没有gaps。 因此最优的窗口大小依赖于 Round-trip time (RTT). 当窗口比较小的时候, 你将会限制连接的吞吐量。
Head-of-Line Blocking 线头阻塞
线头阻塞使我们的应用程序避免了对数据包进行重新排序, 使得应用程序代码容易编写。 可是, 这会引入不确定的延时,以等待丢失的数据包重发。 这种延时可以称为 (jjitter, http://blog.csdn.net/junllee/article/details/6110912) ,这影响到程序的性能。
Packet Loss is OK (丢包的发生也是有好处的)
实际上, 数据包的丢失会使TCP获得更好的性能。 丢失的包作为一种反馈机制,TCP知道网络拥塞, 就会调整接收者和发送者之间的发送速率。 避免造成整个网络瘫痪。 而且, 有的应用程序可以忍受数据包丢失: audio, video , 游戏状态更新。 它们都不需要可靠和有序的数据传递。 顺便说一样, 这也是什么WebRTC(网页实时通信()是基于UDP作为传输协议的原因.
如果一个包丢失, 视频解码器会简单的在视频中插入一小段空白, 并继续处理之后的处据包, 如果这个空隙非常小,视频解码都不会通知使用者,这样就不用在视频输出的时候, 以暂停的方式等待丢失的包。
同样的,如果我们正在传递的数据是 3D游戏中一个角色的状态更新, 这时我们等待的数据是用来描述 T-1 时间点的状态, 而T时间点的数据包已经接收,那么T-1 秒的数据包就是不需要的了。 理想的是,我们可以接收到每一个状态更新, 但是为了保证游戏的不发生延迟。 我们可以接爱间歇性的丢包, 以保证较低的延迟.
TCP 优化
TCP 协议是一种自适应的协议,以保证公平的对待网络的各节点,并最有效的利用各种网络。(公平, 有效利用)。 因此, 优化TCP最好的方法是让TCP知道当前的网络条件, 并且依据上次和下层的协议类型和要求,调整TCP的行为。 比如, 无线网络,它需要不同的拥塞算法; 有的应用程序为了达到最好的体验,会自定义QoS语义。不同的应用程序有不同的要求, 而每一个TCP算法,也有很多影响因子,这些都导致TCP的优化,成为了学术和商业研究中永恒的课题。 在目前为前,我们只是简单的介绍了影响到TCP 性能的一些因素。 当然还有一些其它的因素,比如 selctive acknowledgments(SACK 选择性确认), delayed acknowledgments (迟延性确认), 快速重发等等。 这使得每个TCP会话都变得复杂, 难以理解, 分析, 调整。
虽然每一个TCP算法都有自已特定的细节,并继续发展出不同的反馈(feedback)机制,但核心的原理依然相同, 这些原理导致的性能问题也没有改变:
- TCP 三次握手 引入的延迟 2 RTT
- TCP slow-start 都会发生在每个新的TCP连接
- TCP 流量 和 拥塞控制, 影响到所有TCP连接的吞吐量(带宽)
- TCP 吞吐量会受到cwnd(拥塞窗口)大小的影响
因此, 每个tcp 连接在现代化的高速网络下,传送数据的速率也是受限于 发送者和接受者之间的 roundtrip 时间的影响(可以看看带宽迟延积)。或者这么解释, 虽然可以无限制的增加带宽, 采用光速传送数据,从New York 到 London的迟延还是有28ms. 在很多情况下,TCP的瓶颈不是带宽的大小,而是延迟的大小。 可以查看图2-5.
调优服务器配置
- 提升TCP‘s 的初始化拥塞窗口的大小 原书 26页: cwnd 在一开始如果有一个很大的值, 它允许TCP 在第一次RTT内传递更多的数据。 同时意味着它可以加速window的增长。 特别是 在优化突发性,短连接中起来决定性的作用。
- Slow-Start Restart 原书23页 : 禁用在TCP闲置时,进行慢启动。 这将明显的做优化长连接TCP的性能。
- Window Scaling 增加 接收窗口的大小 原书18 : 增加 rwnd 大小,达到网络最好的吞吐量
- TCP 快速打开 原书 16: 在某些情况下, 允许应用程序数据可以在初始的SYN包,一起发送。 TFO是一种新的优化方式, 要求在客户端和服务器都支持。
调优应用程序行为
- 尽量减小要发送数据的大小
- 我们不能改变数据发送大小的速度, 但是可以将数据放到离客户端更近的地方。
- TCP 连接的重用,对性能的改善很明显
性能检测清单
- 升级你的系统内核到最新的版本
- 确保cwnd 的大小设置为了 10 MSS
- 禁用 slow-start after idle
- 允许窗口可伸缩
- 消除多余数据的传输
- 压缩传送的数据
- 将服务器部署在离客户最近的地方,减小RTT
- 在可能的情况下, 重用TCP
UDP
TLS
加密、 认证、 完整性
TLS 握手
应用层协议协商(ALPN)
- 客户端向ClientHello消息中,添加一个, 新的ProtocolNameList 字段, 这个字段包含了它所支持的应用协议列表
- 服务器检查ProtocolNameList 字段, 并在返回的SeverHello 消息中包含 ProtocolName字段,包含选中的协议
Server Name Indication (SNI)
为了解决这个问题, Server Name Indication(SNI) 作为一个扩展,被引入到TLS协议, 它允许客户端在握手开始的时候,通过hostname字段,标识出它想要建立服务器的名称。 web 服务器会检查SNI 中的 hostname. 并选择合适的证书, 并继续完成握手。
但在许多老的客户端并不支持SNI, 比如运行在 Windows XP的浏览器, Android 2.2 等。
TLS Session Resumption
在所有使用安全通信的应用程序中, 握手阶段导致了额外的延迟和CPU计算, 严重的影响了应用程序的性能。为了减小相同的浪费, TLS 提供了在多个连接中,共用相同的安全密钥(生成的对称密钥)的能力。
Session Indentifiers
会话标识重用是在SSL 2.0版本被引入, 它充许服务器创建并发送 32 字节的会话标识,作为 ”ServerHello" 消息的一部份。
在服务器内部,它需要为每一个客户端缓存 session ID 以及TLS连接参数 (key, 加密算法)。 同样, 客户端也需要保存sessionID 信息, 并且在下次的TLS请求中, 在ClientHello 消息中带上sessionID. 当服务器收到sessionID时, 就知道客户端依然保存了之前加密套件和 从上次握手所获得的 对称密钥。如图 4-3所示, 一个简短的握手过程。如果双方没有从缓存在找到之前的信息,那么将会产生一个新的 session ID.
图4-3 简短的握手过程
利用会话标识,让我们减小了一个RTT时间, 以及计算开销。
可是,在实际的工作中,使用服务器创建和保存session 标识符 具有局限性。 比如每天都会有成千上万的连接需要保存到缓存,这会导致缓存被使用完毕, 有的网站是采用多个服务器,如果共享这些session也是一个问题。 (session 保存在服务器会带来哪些实际问题,可以查看 http://nil-zhang.iteye.com/blog/1279214)
Session Tickets
会话船票会保存在客户端, 在之后的连接中,会以 SessionTick 扩展,包含进ClientHello 信息中。 这样所有的信息就仅保存在客户端, 而且 ticket 依然是安全的, 因为它是使用服务器才知道的密钥加密的。
session indentifiers 和 session ticket 机制可以分别称为 会话缓存 和 无状态恢复。 无状态恢复的最大改进是不需要服务器端缓存, 客户端只需在新的连接中提供 session ticket, 除非ticket 已经过期。
信息链 和 CA
我们怎样才能知道,在建立加密遂道是我们信任的一方,而不是一个攻击者, 所以身份证认证在TLS 连接中是不可分割的一部分。为了知道如何验证双方的身份, 我们举了在 Alice 和 Bob 之间的一个例子:
- Alice 和 Bob 都有自己的公共密钥和私有密钥
- 双方都会隐藏自己的私有密钥
- Alice 向 Bob 分享自己的公共密钥, Bob 也向 Alice 分享了自己的公共密钥
- Alice 向 Bob 发了一条消息,这条信息是用她自己的私有密钥加密的
- Bob 使用 Alice‘s 的公共密钥来校对信息提供者的签名, 这条消息是由Alice发送过来,而不是其它人
信任是交换数据之前的关键, 公钥加密算法,允许我们使用信息发送者的公共密钥, 确认被签名的信息是正确人发送过来的。 但这种信任完全是基于 Alice 和 Bob 相互认识的基础上,才交换的公共密钥。
下一步, Alice 从Clarlie那里接收到一条信息, Alice 没有见过Clarlie, 但Clarlie 声称它是 Bob‘s 的朋友。 实际上, Clarlie 为了证明自己是Bob的朋友, Clarlie 要求Bob 对自己的公开密钥 使用 Bob 的私有密钥进行签时。 并以消息的附件,一起发送给Alice. 如图4-4. 在这种情况下, Alice 首先确认 Clarlie的公共密钥中Bob的签名。 她已经有了Bob 的公开密钥, 所以知道Clarlie 的公开密钥的确是由 Bob签过的。Alice 接受了这条消息, 并使用消息中的Clarlie 公开密钥, 确证发送条消息的人确实是Clarlie.
图4-4
互联网和你的浏览器之间的认证也是采用相同的处理, 浏览器可以通过以下几种方式,添加信任
- 手动指定证书: 每一个浏览器和操作系统都提供了一种机制,你可以手动的导入你信息的证书
- CA认证中心的证书
- 浏览器和操作系统自带的证书
在实际中,你不可能存放所有网站的证书。因此最通用的解决方法是使用CAs来帮我们进行校验. 如图 4-5. 网站会让认证中心对他的名字和公钥进行签名,并将这个签名发送到浏览器,如果浏览器的CAs的根目录下,有认证中心的公开密钥,就能确认这个网站是真实的。
图4-5
图 4-6
Certificate Revocation
不重要,忽略
TLS Record Protocol
不同于IP 或者 TCP协议, 在TLS会话中的所有数据交换都是由TSL Record协议负责。 该协议需要负责识别不同类型的消息(握手信息,警告,数据), 并校对每一个信息的完整性。
图 4-8 TLS record 结构
传递应用程序的数据的流程如下:
- Record 协议获得应用程序的数据
- 对接收到的数据进行分块: 最大为2的14次 bytes 或者16KB一个记录
- 应用数据可以选择压缩
- Message autentication code(MAC) 或 HMAC, 添加消息的校验码
- 使用协商好的加密算法,对数据进行加密
完成以上步聚, 被加密的数据就会向下传入TCP层,进行传输。 在接收端, 则是相反的过程。
这个过程看起来很简单,但需要注间几个地方:
- TLS record 最大为16KB
- 每一个记录会包含 5-byte header, 一个MAC (SSLv3, TLS1.0, TLS1.1 为 20 bytes, TLS 1.2 需要32字节), 如果使用了块分组密码, 还需要把密码加上。
- 为了解密和校对record, 整个record 必须有效。
为你的应用程序挑选出最佳的record 的大小,对性能的优化非常重要,小的records 会导致很大的开销, 然而大的records 会被TCP重新进行组装
优化TLS
TLS的优化主要是配置你的服务器,比如TLS records 的大小, 内存缓冲区, 证书的大小, 是否支持简短的握手等等, 通过对这些参数的正确配置,会明显改善用户体验,降低操作开销。
计算开销
我们之前说过, 使用公钥加密 对比 对称密钥加密 会导致非常大的计算开销。 早先的网站通常需要额外的硬件来完成 SSl计算。但现在的硬件的性能有很大的提升,都能直接完成
但对于TLS Session 的恢复, 还是可以减少使用公钥加密。 除低计算的开销。
提前终止
不管是新建还是重用一个连接,都会有延迟的发生, 对于优化来说, 连接的建立是一个重要的区域。 我们看看一个TLS连接有哪些: TCP 三次握手, 之后是TLS握手, 增加两个RTT时间(新建)。 或者一个RTT(重用)。
在最坏的情况下,数据交换之前, TCP 和 TLS连接的设置过程就将花费三个 RTT. 以我们之前New York到London的例子, 一个RTT 时间为56ms, 那么新建一个TLS 将花费168ms, 而重用一个将花费112ms.
因为TLS是运行在TCP之上,所以对TCP的优化,也适用于这里。 通过CDN将服务器位置到离客户端最近的地方,减小RTT的值。 CDN不仅可以优化静态资源,你也可以应用动态内容上,提前终止TLS 会话。 如图 4-9所示,在本地代理服务器上,建立与客户端的连接(加速完成握手),代理服务器与原始服务器之间建立一个长久(没有握手,只负责数据传送),加密的连接。这样所有的请求和响应都是从原服务器获取
图4-9 提前终止客户端连接
要做到这一点非常容易,很多CDN都提供这样的服务, 你也喜欢冒险,也可以以最小的花费搭建自己的基础设施: 在全球各地的数据中心部署云服务器,并配置代理服务器,将请求转发到你的原始服务器中。 并可以加入基于地理位置的DNS 负载均衡。
Session Caching and Stateless Resumption
了解概念即可TLS Record Size
- 电路中传输的数据包, 会包含IPv4 20bytes 地址, 如果是IPv6会是40 bytes
- TCP 会有20 bytes头部信息
- 40 个字节的TCP 可选信息, 比如 timestamps, SACKs
MTU通用的大小为1500 bytes, 那么在IPv4中 TLS record 大小就是1420 (1500- IP 20 - TCP 20 - TCP Option 40), 而对于IPv6,这个大小为1400. 为了兼容未来, 所以最佳的值为1400.
如果你的服务器需要处理大量的TLS 连接,则需要为每个连接分配最小的内存使用。 OpenSSL 通常会为每个连接分配50KB 的内存, 但正如调整record 大小一样,最好还是查看OpenSSL 的文档。 Google的服务器能常将OpenSSL的值设置为5KB.
TLS 压缩
TLS 内部通过 record协议, 支持对传输的数据进行无损压缩: 压缩的算法是通过握手协议,双方协商好的。 压缩会发生在对每条record 加密之前。 但通常,你要禁止服务器的压TLS 压缩功能, 有以下几个原因:
- 在2012年, 黑客利用过TLS 压缩获得加密认证的cookie, 这就允许黑客执行会劫持, 这种功能方法叫作"CRIME"
- 传输级别上的TLS 不知道要压缩的内容是什么,可能会压缩已经压缩过的数据,比如,图片,视频
两次的压缩,会浪费服务器和客户端的CPU, 而且导致非常严重的安全问题, 虽然很多的浏览器禁用了TLS 压缩,但为了更好的保护你的客户, 你需要明确的禁止服务器压缩.
认证链长度
浏览器验证证书的过程是: 从网站的证书开始, 在遍历父证书, 直到信任的根目录( 证书是分级的,全球, 国家, 省份). 因此,第一条优化的原则是,在服务器上配置所有的中级证书。 如果忘记了, 那么许多浏览器依然可以工作,但是它会暂停, 等待, 并向中间证书的服务器, 获取中间证书, 然后继续验证,直至根目录信任的证书。 这过程中会有新的DNS 查询, TCP连接, 和 HTTP GET请求, 会有数百毫秒的握手延迟。
可是浏览器怎么知道如何获取中间证书的呢? 每一个子证书通常都包含了父证书的URL
相反的, 你必须在你的信任链里不会包含没必要的证书, 或者通俗点说, 你应该减少信任链的大小。 回忆一下, 服务器在TLS 握手期间, 会向客户端发送证书, 很有可能证书的发送是在发生在新TCP连接的slow-start 阶段。 如果证书链的大小超过了TCP 初始 cwnd 的大小, 则只能先发送一部份(TCP cwnd大小), 等待客户端的 ACK后,在发送剩下的部分, 这就会导致多个RTT时间。 如图 4-11
图4-11 WireShack 软件截图 TLS 证书链的大小为 5, 323-byte
图4-11中, 证书链超过了5KB的大小,它超过了一些的老的服务器的拥塞窗口的大小。这就在握手阶段引入了其它RTT的大小。 有一个解决方案是增加初始的拥塞窗口的大小。 可以查看 "Increasing TCP‘s Initial Congestion Window " 原书 26页。 另外, 你也可以减少证书的大小:- 减小认证的层级, 理想的是只包含你自己的证书和中间人证书,第三个证书就是根目录证书(浏览器已经信任, 不需要在发送)
- 不要发送root 证书, 如果你的浏览器连root证书都没有,那么你发了,它也会不信任它
- 将证书链的大小减小到 2 KB 或 者3KB, 虽然为浏览器提供所有必要的证书,可以避免浏览器自己发起新的连接,获取中间代理人证书,从而引发不必要的RTT. 但这可能也就发生一次,而过大的证书会导致每一次新的TLS连接,有会有额外的RTT, 这种性能损失更大
OCSP 装订
针对OCSP的优化, 每一个新的TLS连接都需要浏览器检测认证链,但有一个步聚我们不能忘记, 那就是浏览器还需要检测证书是否被呆销,在这里,浏览器有两个方法,一个是下载CRL文件并且缓存, 还有一个就是通过OCSP进行实时的检测, 从认证中心的服务器获取响应, 并使用认证中心的公开对响应进行加密。 以证实证书是否吊销。所以我们可以在服务器 发送一个请求到认证中心的响应,并且把它包含(装订)到认识链中(部分浏览器可以识别)。 这样我们自己的服务器就可以缓存被签名过的OCSP响应,可以节省多个客户端的额外请求。 但这里也需要注意一些问题:- OCSP的大小从400到 4000不等, 装订到你的认证链中, 可能会超过你TCP拥塞窗口的大小
- 只能装订一个OCSP响应, 那么对于中间(发证中心)的证书,浏览器还是会发送OCSP请求。
最近,你需要在配置服务器,允许有OCSP装订的功能。 好的消息是,主流的服务器, 比如Nginx, Apache, IIS是有这个功能的。 你可以查看这些服务器的文档说明。
HTTP Strict Transport Security (HSTS)
HTTP Strict Transport Security 是一个安全策略机制, 它允许服务器通过简单的HTTP 头, 比如 Strict-Transport-Security: max-age = 3153600. 向顺从的浏览器(知道这类http头的浏览器) 声明访问规则。 这条规则表示,客户端必须强制执行以下规则:
- 所有到初始请求,都必须使用HTTPS
- 所有不安全的连接 和客户端请求,在请求发送之前, 应该自动转化为 HTTPS
- 如果发生认识错误, 显示错误信息, 并且不允许绕过吃警告
- max-age 表明 HSTS规则的有效期, 单位为秒
HSTS 不仅可以将原始连接转变为 HTTPS, 还可以保护应程序, 在性能上, 可以减小从 HTTP-to-HTTS的跳转(301 或者302)。
在2013年时, 支持HSTS的浏览器有 Firefox 4+, Chrome 4+, Opera 12+ 以及Android 版的Chrome 和 Firefox. 最新的了解可以查看 caniuse.com/stricttransportsecurity.
TLS 性能检测清单
- 优化TCP, 可以查看 "Optimizing for TCP" 在原书第32
- 升级TLS 库到最新的版本
- 允许并配置 TLS session 的缓存 和 无状态重用
- 监测你的TLS session 缓存命中率, 并调整相应的配置
- 使用CDN, 在客户端最近的寺方, 提前终止 TLS sesion, 降低往返导致的延迟
- 配置TLS record 的大小,以适应单个TCP段的大小, 1400 bytes
- 确保你的认证不会超过初始化拥塞窗口的大小, 低于 2 or 3KB
- 从认证链中删除没必要的证书, 减小认证链的深度
- 禁用服务器的TLS 压缩
- 配置你的服务器,以支持SNI, 域名识别
- 配置OCSP 装订功能
- 添加 HTTP Strict Transport Security header
测试和检验
最后, 检验和测试你的配置, 你可以使用在线的服务, 比如 Qualys SSL Server Test 扫描你公开的服务器的通用配置 和安全缺陷。 另外,你也可以使用 openssl 命令, 它将帮助你检验整个握手过程和本地的服务器配置
$> openssl s_client -state -CAfile startssl.ca.crt -connect igvita.com:443 CONNECTED(00000003) SSL_connect:before/connect initialization SSL_connect:SSLv2/v3 write client hello A SSL_connect:SSLv3 read server hello A depth=2 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing /CN=StartCom Certification Authority verify return:1 depth=1 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing /CN=StartCom Class 1 Primary Intermediate Server CA verify return:1 depth=0 /description=ABjQuqt3nPv7ebEG/C=US /CN=www.igvita.com/emailAddress=ilya@igvita.com verify return:1 SSL_connect:SSLv3 read server certificate A SSL_connect:SSLv3 read server done A SSL_connect:SSLv3 write client key exchange A SSL_connect:SSLv3 write change cipher spec A SSL_connect:SSLv3 write finished A SSL_connect:SSLv3 flush data SSL_connect:SSLv3 read finished A --- Certificate chain 0 s:/description=ABjQuqt3nPv7ebEG/C=US /CN=www.igvita.com/emailAddress=ilya@igvita.com i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing /CN=StartCom Class 1 Primary Intermediate Server CA 1 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing /CN=StartCom Class 1 Primary Intermediate Server CA i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing /CN=StartCom Certification Authority --- Server certificate -----BEGIN CERTIFICATE----- ... snip ... --- No client certificate CA names sent --- SSL handshake has read 3571 bytes and written 444 bytes --- New, TLSv1/SSLv3, Cipher is RC4-SHA Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1 Cipher : RC4-SHA Session-ID: 269349C84A4702EFA7 ... Session-ID-ctx: Master-Key: 1F5F5F33D50BE6228A ... Key-Arg : None Start Time: 1354037095 Timeout : 300 (sec) Verify return code: 0 (ok)
- 客户端完成 认证链的验证
- 接收到认证链(两个证书)
- 接收到的认证链的大小
- 有状态的TLS session 标识符
在此之例子中, 我们通过TLS的默认端口(443) 连接到 igvita.com , 并执行TLS 握手。 由于 s_client 不清楚root证书,我们需要手动将 StartSSL Certifiecate Authority 导入到根目录(非常重要的一步) , 否则 s_client 会看到一个校验失败的错误日志。
检测证书链的过程中,我们看到服务器发送了两个证书, 总的大小为3,571 bytes, 非常接进 3 到 4 个段 (TCP 老的服务器初始拥塞窗口的大小为 4 个段) 。最后, 我们检测的协商 SSL 的变量 - 最新的协议, 加密算法 , 对称密钥 - 我们也可以看到服务器发磅了一个session 标识。
High Performance Browser Networking - TCP UDP TLS