332 lines
16 KiB
Markdown
332 lines
16 KiB
Markdown
# sdlan交互协议
|
||
sdlan协议的总体格式如下:
|
||
|
||
```
|
||
+-----------+------------+--------+----------+
|
||
| 2字节的长度 | 2字节消息id | 消息头 | 消息体 |
|
||
+-----------+------------+--------+----------+
|
||
```
|
||
|
||
## 1. 长度
|
||
2字节长度包含后面的所有字节的长度(不包括自身)
|
||
|
||
## 2. 消息id
|
||
用于匹配请求和响应,占用2字节,目前可以都先填写0
|
||
|
||
## 3. 消息头
|
||
在sdlan中,有一个common头使用二进制协议,消息头主要包含了协议版本,消息来源id(从哪个节点发出的节点标识),消息ttl,以及一些协议的flag(flag标志以后可以扩充)
|
||
|
||
消息头二进制格式如下:
|
||
|
||
```
|
||
+---------+----+-------+-----+------+-----+
|
||
| version | id | token | ttl | flag | pc |
|
||
+---------+----+-------+-----+------+-----+
|
||
```
|
||
|
||
其中,version占用一个字节,用于标识协议版本。id用于唯一标识某个客户端,为长度为32字节的uuid,token是用户生成的邀请码,占用8个字节(64比特的大端序整型数字)。之后的ttl占用一个字节,当ttl为0,则直接丢弃该数据包。后面的flag占用2字节,用于标识数据包属性,目前拥有的标识如下:
|
||
|
||
* `federation`: `0x0010`,表示这个数据包是从其他的supernode主动发送过来的,比如,其他supernode主动发送过来的RegisterSuper;其他supernode收到情短请求之后,主动将请求以Command的形式广播给其他supernode等。
|
||
* `from_sn`: `0x0020`,表示这个数据包是从服务端发送过来的。
|
||
* `socket`: `0x0040`,表示这个数据包里面包含了有用的socket信息(通常在服务端转发的时候有用)。
|
||
* `v6_info`: `0x0080`,表示这个数据包里面包含了ipv6信息(专门用在packet数据包里面,目前只有这个数据包使用了该标识)。
|
||
|
||
最后的pc表示后面数据包的类型,目前数据包类型如下:
|
||
|
||
* `0` —— `invalid pkt type`,表示无效的数据包类型
|
||
* `1` —— `register super`,表示客户端像服务端发送的注册请求
|
||
* `2` —— `packet`,表示客户端向服务端(或者客户端发送给客户端)发送的来自tun的数据包。
|
||
* `3` —— `register`数据包,表示客户端之间打洞请求消息
|
||
* `4` —— `register_ack`数据包,表示客户端之间打洞ACK消息
|
||
* `5` —— `register_super_ack`数据包,表示可以对节节点进行授权,返回对应的ip地址等信息
|
||
* `6` —— `register_super_acknowledge`数据包,表示对`register_super`的一般返回,只是告诉客户端:服务端收到他的注册请求了
|
||
* `7` —— `register_super_nak`数据包,表示服务端不接受该客户端的上线请求,客户端应该直接退出
|
||
* `8` —— `unregister_super`数据包,客户端告诉服务端,客户端自己要下线了。
|
||
* `9` —— `query_info`数据包,客户端向服务端查询对端的ip信息
|
||
* `10` —— `peer_info`数据包,服务端向客户端返回对端的ip等信息。
|
||
|
||
## 4. 消息体
|
||
消息提根据协议头中的pc不同,拥有不同的结构,结构体在json化之后,需要加密的数据包进行加密,然后将内容添加到消息体的位置。
|
||
|
||
其中,RegisterSuper, RegisterSuperACK, RegisterSuperAcknowledge, RegisterSuperNAK,这几个消息,在发送和接收的时候,消息提内容没有进行加密,直接是json序列化之后的内容(为了提高安全性,服务端和客户端在实际编写的时候,可以商量一个预置的aes密钥,这几个消息类型就用这个密钥进行加解密)。
|
||
|
||
在服务端返回RegisterSuperACK的时候,会有`enctypted_key`和`header_key`。这两个字段为使用客户端rsa公钥加密之后的两个aes密钥。
|
||
其中:
|
||
|
||
* `encrypted_key`:用于加密PACKET数据包中的data流量,也就是用于加密tun网卡的流量。
|
||
* `header_key`:用于加密之后传输的结构本身,比如客户端在向服务端发送一个PACKET数据包的时候,在将json序列化之后,再将序列化之后的内容使用`header_key`aes再加密一次,然后放到消息提的位置。
|
||
|
||
### 4.1. RegisterSuper包类型
|
||
`register super`,pc为1,表示客户端像服务端发送的注册请求,该请求每隔15-20秒发送一次,RegisterSuper的json结构如下(该结构没有用aes加密,为了安全,以后如果没有加密的结构体,可以预置一个32为aes密钥):
|
||
|
||
```json
|
||
{
|
||
"pass": "初始密钥,用于最初始的简单验证, 目前固定为`encrypt!`",
|
||
// 客户端自己随机生成的整数,用于匹配RegisterSuper和RegisterSuperACK, RegisterSuperNAK, RegisterSuperAcknowledge
|
||
"cookie": $uint32,
|
||
|
||
// sock在客户端发送时不需要填写,在Supernode之间转发的时候,会带上这个,表示客户端的信息。
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
},
|
||
// 客户端自己的ipv6信息,如果没有ipv6,则不用填写
|
||
"v6_info": {
|
||
// 客户端的ipv6的端口
|
||
"port": $uint16,
|
||
// 客户端的ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],
|
||
},
|
||
// 客户端tun设备的ip信息
|
||
"dev_addr": {
|
||
// 客户端的tun的ip地址,如果没有ip(第一次上线等情况),则填写0
|
||
"net_addr": $uint32,
|
||
// 子网掩码的位数,一般为24, 如果没有ip,则填写0
|
||
"net_bit_len": $uint8,
|
||
},
|
||
// 客户端自己的公钥,在服务端返回加密的aes密钥时,会使用这个公钥加密
|
||
"pub_key": "",
|
||
// 字符串,用于标识一个用户的id,或者一个由用户生成的token
|
||
"token": "",
|
||
}
|
||
```
|
||
|
||
|
||
### 4.2. Packet数据包
|
||
`packet`,pc为2,表示客户端向服务端(或者客户端发送给客户端)发送的来自tun的数据包。packet数据包的json化之后的结构如下:
|
||
|
||
```json
|
||
{
|
||
// 来源ip
|
||
"src_ip": $uint32,
|
||
// 目的ip
|
||
"dst_ip": $uint32,
|
||
// sock信息, sock信息包含了来自客户端的信息
|
||
// 通常客户端发送一个packet数据包的时候,不需要携带这个信息。
|
||
// 而在服务端收到一个packet的数据包的时候,在转发的时候,会将客户端的信息放入这个结构,然后发送给需要转发的客户端。所以在客户端收到转发的packet数据包的时候,可以通过这个sock结构,得知发送的客户端的端口信息。在sock结构有用的时候,flags中的socket(0x0040)标识也会被置位。
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
},
|
||
// 如果对端有ipv6,则填写该结构,否则就省略
|
||
"v6_info": {
|
||
// ipv6的端口
|
||
"port": $uint16,
|
||
// v6
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
}
|
||
// data为二进制的字节数组,为aes加密之后的tun流量。
|
||
"data": []
|
||
}
|
||
```
|
||
|
||
其中,`src_ip`为tun的ip数据包的来源ip,`dst_ip`为tun的ip数据包的目的ip。均为大端序的32位无符号整数。`sock`为转发时由sn填充的发送客户端的信息,发送短不需要处理。`data`为tun的数据流量,使用`network_pass`进行加密后的二进制数据。
|
||
|
||
在发送packet数据包之前,消息体部分的数据,为json化之后的packet使用`header_pass`进行aes加密后的二进制数据。
|
||
|
||
在客户端收到PACKET消息的时候,如果数据包来自服务端(转发过来的),则可以向sock字段表示的对端信息发送register消息
|
||
|
||
在实际发送的时候,为了效率,packet数据包被直接打包成二进制进行处理。二进制规则如下:
|
||
|
||
1. 在消息头的`flags`里面会给相应的标志位置位,比如,如果有sock信息,则flags会有`SOCKET`标识,如果有`ipv6`信息,则会有`v6_info`标识。
|
||
2. 消息体序列化`src_ip`,大端序表示`uint32`的来源ip地址
|
||
2. 消息体序列化`dst_ip`,大端序表示`uint32`的目的ip地址
|
||
3. 如果消息头的`socket`置位了,接下来就是序列化sock信息:
|
||
1. 一个字节的family
|
||
2. 2个字节(大端序的`uint16`)端口号
|
||
1. 如果family是2(表示是ipv4),则接下来是4个字节的ipv4
|
||
2. 如果family是10(表示是ipv6,目前不会有这种情况,只是为了兼容以后服务端也支持ipv6的情况),则接下来是16个字节的ipv6地址
|
||
4. 如果消息头的`v6_info`置位了,则接下来就是ipv6信息:
|
||
1. 2个字节(大端序的`uint16`)ipv6端口号
|
||
2. 16个字节的ipv6地址
|
||
5. 之后是`network_pass`加密后的二进制tun接口的流量数据。
|
||
|
||
### 4.3. Register数据包
|
||
`register`,pc为3,客户端向另一个客户端发送打洞请求的数据包。在完全不知情的情况下,当一个客户端需要向另一个客户端发送数据的时候(tun接口的流量),发送端首先查看自己是否知道对方的地址,此时不知道,发送端就向服务端发起一个QueryInfo的数据包,同时,发送端的tun数据包首先通过服务端中转到对端客户端,一方面,发送端在收到QueryInfo的返回PeerInfo数据包的时候,会向接收端发起一个(或者一系列)Register数据包;另一方面,接收端在收到Packet数据包之后,也会发起一个(或者一系列)Register数据包。两边同时发起Register进行打洞,当有一个数据包能被正确接收,则马上返回RegisterACK数据包,两边打洞成功。在json格式如下:
|
||
|
||
```json
|
||
{
|
||
"cookie": $uint32,
|
||
// 源tun ip
|
||
"src_ip": $uint32,
|
||
// 目的tun ip
|
||
"dst_ip": $uint32,
|
||
// supernode转发时开到的发送者的外网ip信息,发送的时候,不需要填写
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
},
|
||
// 如果没有ipv6,则不用填写
|
||
"v6_info": {
|
||
"port": $uint16,
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.4. RegisterACK数据包
|
||
pc为4,结构如下:
|
||
|
||
```json
|
||
{
|
||
// 匹配Register
|
||
"cookie": $uint32,
|
||
// 源tun ip
|
||
"src_ip": $uint32,
|
||
// 目的tun ip
|
||
"dst_ip": &uint32,
|
||
// 发送者的外网ip信息
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.5. RegisterSuperACK数据包
|
||
pc为5。在服务端收到registersuper之后,如果服务端判断该节点已经授权,而且是刚上线,就会返回一个RegisterSuperACK,告知该节点ip信息。另外,如果在服务端界面上进行操作,将本来没有授权的节点进行授权,或者将已将寂静授权的节点,转移到另一个网络里面,服务端也会主动向客户端发送一个RegisterSuperAck消息,将客户端需要的信息(新网络的加密aes等)主动告诉客户端。
|
||
|
||
RegisterSuperACK的消息格式如下(暂时该结构没有加密,以后可以将该结构整个用客户端的rsa公钥加密):
|
||
|
||
```json
|
||
{
|
||
// 匹配RegisterSuper
|
||
"cookie": $uint32,
|
||
|
||
// 服务端看来的该客户端的外网ip信息
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
},
|
||
// 服务端给客户端分配的tun设备的ip信息
|
||
"dev_addr": {
|
||
// 客户端分配到的ip地址
|
||
"net_addr": $uint32,
|
||
// 客户端分配到的ip地址的掩码位数
|
||
"net_bit_len": $uint8,
|
||
},
|
||
// rsa加密之后的加密头的aes密钥
|
||
"header_key": "",
|
||
// rsa加密之后的tun流量的加密aes密钥
|
||
"encrypted_key": "",
|
||
// 多久之后,客户端应该再发起RegisterSuper消息,暂时可以忽略
|
||
"lifetime": $int16
|
||
}
|
||
```
|
||
|
||
### 4.6. RegisterSuperAcknowledge数据包
|
||
在服务端收到registersuper之后,如果服务端判断该节点已经授权,而且不是第一次收到该节点的RegisterSuper消息(这个时候,该RegisterSuper消息类似于心跳数据包了),就会返回一个RegisterSuperAcknowledge,告知该节点:服务端已经收到你的消息了。另外,如果服务端判断该节点没有授权,也会返回一个RegisterSuperAcknowledge消息,这个时候,可以理解为:服务端收到你的消息了,但是你没有授权,我这边不会给你分配ip,你就这样等着吧。
|
||
|
||
RegisterSuperAcknowledge的消息格式如下(该结构没有用aes加密):
|
||
|
||
```json
|
||
{
|
||
// 来自registerSuper消息的cookie
|
||
"cookie": $uint32,
|
||
}
|
||
```
|
||
|
||
### 4.7. RegisterSuperNAK数据包
|
||
服务端在节点发送RegisterSuper时候,如果该数据包里面的初始验证信息等无效,则会直接返回RegisterSuperNAK消息,此时客户端应该直接退出。
|
||
|
||
量外,如果服务端判断到,比如一个用户最多可以创建10个客户端,但是这个节点是第11个了,在收到该客户端的RegisterSuper以后,和会直接返回RegisterSuperNAK消息,客户端同样应该直接退出。
|
||
|
||
RegisterSuperNAK结构如下(该结构没有用aes加密):
|
||
|
||
```json
|
||
{
|
||
// 来自registerSuper消息的cookie
|
||
"cookie": $uint32,
|
||
}
|
||
```
|
||
|
||
### 4.8. UnregisterSuper数据包
|
||
当客户端没有收到RegisterSuperNAK的情况下(没有被服务端拒绝接入),在客户端结束之前,可以发送UnregisterSuper数据包,该数据包告诉服务端,客户端将要下线了,可以将这个客户端的相关信息删除了。
|
||
|
||
UnregisterSuper数据包结构如下:
|
||
|
||
```json
|
||
{
|
||
// 客户端被分配的ip,如果没有分配到,则设置为0
|
||
"src_ip": $uint32
|
||
}
|
||
```
|
||
|
||
### 4.9. QueryInfo数据包
|
||
在发送端发送数据包PACKET消息的时候,如果对方ip找不到(没有打洞),则会发起一个QueryInfo数据包,服务端在收到相应信息之后,会返回锁查找的接收端的信息PEERINFO数据包,发送端收到PEERINFO之后,便发起Register进行打洞。
|
||
|
||
结构如下:
|
||
|
||
```json
|
||
{
|
||
// 自身tun ip
|
||
"src_ip": $uint32,
|
||
// 需要查找的目标的tun ip
|
||
"dst_ip": $uint32,
|
||
}
|
||
```
|
||
|
||
### 4.10. PeerInfo数据包
|
||
PeerInfo结构如下:
|
||
|
||
```json
|
||
{
|
||
// 标识为,暂时没有用,都填写0
|
||
"flag": $uint16,
|
||
// 之前发起短的tun ip
|
||
"src_ip": $uint32,
|
||
// 目标的tun ip
|
||
"dst_ip": $uint32,
|
||
|
||
// 需要查找的对端的外网通信信息
|
||
"sock": {
|
||
// family表示客户端的ip协议版本,2表示ipv4,10表示ipv6
|
||
"family": 2|10,
|
||
// 客户端开放的端口, u16
|
||
"port": $uint16,
|
||
// 如果family为2,则v4有用,一个四字节的数组,表示ipv4
|
||
"v4": [1,2,3,4],
|
||
// 如果family为10,则v6有用,一个16字节的数组,表示ipv6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
},
|
||
// 朝兆的对端的ipv6信息,如果有的话
|
||
"v6_info": {
|
||
// 端口
|
||
"port": $uint16,
|
||
// v6地址
|
||
"v6": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
|
||
}
|
||
|
||
}
|
||
```
|