2.3 连接握手
常量定义
- handshake code
- 0: 握手成功
- 1: 认证失败,握手失败
- -1: 握手失败
connect
服务器需要对连接的客户端进行认证,UDPack 协议提供了一种简单的 token 认证机制,客户端发送握手请求时携带 token,服务端收到 token 后进行验证,如果合法则继续处理握手请求,否则拒绝连接。客户端创建 UDPack 对象时传入 token。服务端创建 UDPack 对象时,需注册由上层应用程序自定义的 token 验证函数,服务端可通过此函数验证 token 是否合法。
UDPack 是安全传输协议,需要采用对称加密算法对数据进行加密后再传输,因此在创建 Session 的握手过程中,需要协商加密算法及相关参数。为了保持协议简单,客户端和服务端可支持相同加密算法,并由客户端确定使用哪个算法。同时,客户端随机生成加密算法所需其他参数,如 密钥(key)、初始向量(iv) 等。客户端发送握手请求时,携带加密算法及相关参数(algorithm,key,iv)。
为了安全传递上述敏感信息,客户端需事先集成服务端的公钥。服务端创建 UDPack 对象时,传入服务端公钥及私钥。客户端创建 UDPack 对象时,传入服务端公钥。客户端使用服务端的公钥对上述敏感信息进行加密,然后传递给服务端。服务端通过私钥解密数据,获得对称加密算法及参数。
连接握手过程如下:
客户端与服务端分别打开 UDP 套接字,并绑定端口。客户端首先发起连接请求,创建本地 Session ,状态为 OPEN,并发出 shakehand 帧
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
| [random integer] | [0] |
+-------------------------------+-------------------------------+
| timestamp ... |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| ... timestamp [curent time] |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
|S|P|F| RSV |S|F| RSV |OPCODE | Payload len |
|T|A|R| [0] |L|I| [0] |[0xA] | [Payload.length] |
|R|C|A| |O|N| | | |
|E|K|G| |W| | | | |
|0|0|0| |0|0| | | |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
: Payload [algorithm,key,iv,token] :
+---------------------------------------------------------------+
客户端需要随机生成一个 16 位无符号整数,写入 Session ID 字段的高 16 位。低 16 位由服务器生成,此时写入 0 即可。timestamp 字段写入当前时间戳,精确到毫秒。STRE、PACK、FRAG、SLOW、FIN 设置为 0。RSV 字段设置为 0。OPCODE 字段设置为 0xA。
使用服务端公钥加密 [algorithm,key,iv,token] 并写入 Payload 字段,计算 Payload 的字节数写入 Payload len 字段。
服务端收到 shakehand 帧后,通过服务端私钥解密 Payload,解析出加密算法及参数和 token,验证 token,如果合法,则创建 Session,状态为 OPEN,并回复 handshake 帧
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
| [id from client] | [random integer] |
+-------------------------------+-------------------------------+
| timestamp ... |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| ... timestamp [curent time] |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
|S|P|F| RSV |S|F| RSV |OPCODE | Payload len |
|T|A|R| [0] |L|I| [0] |[0xB] | [1] |
|R|C|A| |O|N| | | |
|E|K|G| |W| | | | |
|0|0|0| |0|0| | | |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
|Payload [code] |
+---------------+
服务端需要随机生成一个 16 位无符号整数,写入 Session ID 字段的低 16 位。高 16 写入客户端传过来的值。timestamp 字段写入当前时间戳,精确到毫秒。接下来 STRE、PACK、FRAG、SLOW、FIN 设置为 0。RSV 字段设置为 0。OPCODE 字段设置为 0xB。Payload 写入代表握手成功的 code 值。code 为预先定义的 8 位无符号整数。Payload len 字段写入 1。
验证 token,如果非法,则回复 handshake 帧,Payload 写入代表认证失败的 code,Payload len 字段写入 1,其他字段设置如上所述。
客户端收到 handshake 帧后,读取 Payload 中的 code,认证失败则握手失败,认证成功,则读取服务端生成的 Session ID 的低 16 位,合并高、低 16 位组成 32 位 Session ID 写入 Session 对象,并将 Session 状态更新为 READY,然后回复 handshake 帧
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+-------------------------------+
| [id from client] | [id from server] |
+-------------------------------+-------------------------------+
| timestamp ... |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| ... timestamp [curent time] |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
|S|P|F| RSV |S|F| RSV |OPCODE | Payload len |
|T|A|R| [0] |L|I| [0] |[0xB] | [1] |
|R|C|A| |O|N| | | |
|E|K|G| |W| | | | |
|0|0|0| |0|0| | | |
+-+-+-+-----+-+-+-------+-------+-------------------------------+
|Payload [code] |
+---------------+
帧格式和服务端发送的 handshake 帧一样,其中 Payload 写入代表握手成功的 code,Payload len 写入 1。客户端将 Session 返回给上层应用程序。
服务端收到客户端发送的 handshake 帧后,将 Session 状态更新为 READY,此时连接已建立完成。服务端将 Session 返回给上层应用程序。
超时重试
一段时间后客户端检查本地 Session 对象状态,如果不是 READY 状态,说明没有连接成功,需要进行重试。可以通过选项控制重试次数,在重试次数达到上限后,如果仍然无法完成连接,则关闭会话。并上报上层应用程序连接超时错误。