# sdlan交互协议 sdlan协议的总体格式如下: ``` +-----------+------------+--------+----------+ | 2字节的长度 | 2字节消息id | 消息头 | 消息体 | +-----------+------------+--------+----------+ ``` ## 1. 长度 2字节长度包含后面的所有字节的长度(不包括自身) ## 2. 消息id 用于匹配请求和响应,占用2字节,目前可以都先填写0 ## 3. 消息头 在sdlan中,有一个common头使用二进制协议,消息头主要包含了协议版本,消息来源id(从哪个节点发出的节点标识),消息ttl,以及一些协议的flag(flag标志以后可以扩充) 消息头二进制格式如下: ``` +---------+----+-----+------+-----+ | version | id | ttl | flag | pc | +---------+----+-----+------+-----+ ``` 其中,version占用一个字节,用于标识协议版本。id用于唯一标识某个客户端,为长度为32字节的uuid,之后的ttl占用一个字节,当ttl为0,则直接丢弃该数据包。后面的flag占用2字节,用于标识数据包属性,目前拥有的标识如下: * `from_sn`: `0x0020`,表示这个数据包是从服务端发送过来的。 * `socket`: `0x0040`,表示这个数据包里面包含了有用的socket信息(通常在服务端转发的时候有用)。 最后的pc表示后面数据包的类型,目前数据包类型如下: * `0` —— `invalid pkt type`,表示无效的数据包类型 * `1` —— `register super`,表示客户端像服务端发送的注册请求 * `2` —— `packet`,表示客户端向服务端(或者客户端发送给客户端)发送的来自tun的数据包。 * `3` —— `register`数据包,表示客户端之间打洞请求消息 * `4` —— `register_ack`数据包,表示客户端之间打洞请求消息 * ... TODO ## 4. 消息体 消息提根据协议头中的pc不同,拥有不同的结构,结构体在json化之后,添加到消息体的位置。 ### 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] }, // 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加密后的二进制数据。 ### 4.3. Register数据包 `register`,pc为3,客户端向另一个客户端发送打洞请求的数据包。json格式如下: ```json { // 源ip "src_ip": $uint32, // 目的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] }, // 发送者的tun的ip信息 "dev_addr": { // tun设备的ip,大端序的32位无符号 "net_addr": $uint32, // 子网掩码1的位数,一般为24 "net_bit_len": $uint8, }, } ``` ### 4.4. RegisterACK数据包 pc为4, ### 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.6. RegisterSuperNAK数据包 服务端在节点发送RegisterSuper时候,如果该数据包里面的初始验证信息等无效,则会直接返回RegisterSuperNAK消息,此时客户端应该直接退出。 量外,如果服务端判断到,比如一个用户最多可以创建10个客户端,但是这个节点是第11个了,在收到该客户端的RegisterSuper以后,和会直接返回RegisterSuperNAK消息,客户端同样应该直接退出。 RegisterSuperNAK结构如下(该结构没有用aes加密): ```json { // 来自registerSuper消息的cookie "cookie": $uint32, } ```