备忘。
1. 问题
UDP不可靠,这个引起两个问题
- tls是按顺序加解密的,两端维持有一个seq number,乱了的话,就解密不出来了;这个seq在tls中不是随数据包发送的,而是在两端各自维护。还有种情况是使用流加密,流加密的状态也是在两端维护的。这里导致记录层数据包之间是有隐含关联的。
- tls握手是以消息已经排好顺序为前提的,如果乱了就会认为握手不正确。比如该server发送
ServerHello
,直接发送了Certificate
, client就会认为是unexpected message
,直接终止连接。
2. 处理
2.1. 针对记录层数据包之间的关联性
- DTLS不使用流加密
- DTLS将seq number放到每个数据包,随数据包一起发送,这样对端就能直接取出seq来解密
2.2. 针对握手阶段消息的顺序
- 数据包可能丢失—–>DTLS加上了握手消息重传机制
- 数据包可能重新排序—–>DTLS给握手过程中的每个消息都加上seq number,如果是期望的下一个消息就处理,如果不是就先缓存
- 握手数据包大小可能超过IP数据包分片大小(通常<1500)—–>DTLS允许一个握手数据包分成几个记录层数据包,保证每个记录层数据包都在PMTU之内。记录层数据包不会分成几个IP包;但几个记录层数据包可以在一个IP包中。
2.3. 重放检测
还有个TLS没有的问题,就是DTLS可能会收到重复的数据包,DTLS提供了一种可选的重放检查机制”bitmap”来检查重放,简单来说就是一个类似滑动有效窗口一样的范围,左侧是按顺序收到的最新的一个有效消息对应的seq number,右侧是DTLS认为有效的seq number,落在其中的消息都认为有效,都可以解密,落在窗口右侧的都可以解密校验MAC,校验通过就向右滑动窗口。
2.4. 无状态检查
这个其实DTLS和TLS(1.3)都有,但DTLS是必须的。
UDP没有连接,也没有三次握手,导致应用可以伪造数据包一直发送ClientHello
,如果server每次收到ClientHello
都建立握手状态,然后返回ServerHello
,会导致server端内存很快耗尽。DTLS解决办法是收到 ClientHello
后,返回一个 HelloRetryRequest
的消息,该消息中带有一段”cookie”,其中存放 Cookie = HMAC(Secret, Client-IP, Client-Parameters)
,这是对client IP和加密参数的一个校验值,Secret
只有server才知道,这样就能方式client伪造。然后client再发送一个带上cookie的 ClientHello
,server会再次验证该cookie是否是同一个client发来的,如果不是,server就拒绝连接。在验证第二个ClientHello
有效之前,server不会保存握手状态,这样就可以防止client发起DDoS攻击。
3. 超时重传机制
这里重点介绍下这个超时重传,也就是解决UDP数据包可能丢失的问题。
首先要知道,DTLS只保证握手阶段数据包的可靠传输,应用数据是不保证的——当然DTLS保证即使应用数据包乱序和丢失,也能正常解密收到的数据。
DTLS将握手分为几个”flight”,每个”flight”其实是一端发送的一组消息,这组数据可以连续发出去,中间不会再收取对端的消息。每个”flight”当成一个整体,如果重传,就重传这一组消息。如下划分:
1 | Client Server |
下边是会话恢复时候的flight划分
1 |
|
发送和重传的详细过程例子:client发送”flight 3”消息后,就建立个定时器,如果一段时间没有收到全部的 “flight 4” 消息,client就认为需要重传”flight 3”。server发送完”flight 4”后,也会建立个定时器,如果收到了client重传过来的”flight 3”,就会重传”flight 4”。client在收到全部的”flight 4”后会清理掉”flight 3”的定时器,server在收到全部的”flight 5”消息后,会清理掉”flight 4”的定时器。
如果是最后一个flight,比如完整握手中server发送的”flight 6”,发送完成后,同样需要建立一个定时器,最少是默认MSL的两倍,用来等待 client “flight 5”的重传包或者是应用数据。如果超过了2*MSL或者等来了client发来的应用数据,server就可以清理掉该定时器。会话恢复中则是client的 “flight 3” 建立定时器。
以上的过程可以统一到下边这个状态机中:
1 | +-----------+ |
因为是client先发送数据,所以client一开始进入的是”PREPARING”状态,server一开始进入的是”WAITING”状态。
PS: 重传消息的seq number不变。
4. 其他一些东西
- 最开始的
ClientHello 和 HelloVerifyRequest
不包含在最后CertificateVerify 和 Finished
中HMAC的计算里 - 虽然一个握手消息会分成几个记录层数据包,但计算
CertificateVerify 和 Finished
中的hash的时候,仍会将分开的握手消息分片组成一个完整的握手消息去计算,毕竟计算握手副本hash的时候,用的只是握手数据,不包括记录层。 - 超时重传定时器的值,建议从1s开始,依次*2,最大值不少于 60s。
- 告警信息不会重传。
- epoch是每次加密状态变换的时候递增。
参考: