sdlan-rs/docs/protocol.md

16 KiB
Raw Blame History

sdlan交互协议

sdlan协议的总体格式如下

+-----------+------------+--------+----------+
| 2字节的长度 | 2字节消息id |  消息头 |  消息体   |
+-----------+------------+--------+----------+

1. 长度

2字节长度包含后面的所有字节的长度不包括自身

2. 消息id

用于匹配请求和响应占用2字节目前可以都先填写0

3. 消息头

在sdlan中有一个common头使用二进制协议消息头主要包含了协议版本消息来源id从哪个节点发出的节点标识消息ttl以及一些协议的flagflag标志以后可以扩充

消息头二进制格式如下:

+---------+----+-----+------+-----+
| version | id | ttl | flag | pc  |
+---------+----+-----+------+-----+

其中version占用一个字节用于标识协议版本。id用于唯一标识某个客户端为长度为32字节的uuid之后的ttl占用一个字节当ttl为0则直接丢弃该数据包。后面的flag占用2字节用于标识数据包属性目前拥有的标识如下

  • 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_keyheader_key。这两个字段为使用客户端rsa公钥加密之后的两个aes密钥。 其中:

  • encrypted_key用于加密PACKET数据包中的data流量也就是用于加密tun网卡的流量。
  • header_key用于加密之后传输的结构本身比如客户端在向服务端发送一个PACKET数据包的时候在将json序列化之后再将序列化之后的内容使用header_keyaes再加密一次然后放到消息提的位置。

4.1. RegisterSuper包类型

register superpc为1表示客户端像服务端发送的注册请求该请求每隔15-20秒发送一次RegisterSuper的json结构如下该结构没有用aes加密为了安全以后如果没有加密的结构体可以预置一个32为aes密钥

{
    "pass": "初始密钥,用于最初始的简单验证, 目前固定为`encrypt!`",
    // 客户端自己随机生成的整数用于匹配RegisterSuper和RegisterSuperACK, RegisterSuperNAK, RegisterSuperAcknowledge
    "cookie": $uint32,

    // sock在客户端发送时不需要填写在Supernode之间转发的时候会带上这个表示客户端的信息。
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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数据包

packetpc为2表示客户端向服务端或者客户端发送给客户端发送的来自tun的数据包。packet数据包的json化之后的结构如下

{
    // 来源ip
    "src_ip": $uint32,
    // 目的ip
    "dst_ip": $uint32,
    // sock信息, sock信息包含了来自客户端的信息
    // 通常客户端发送一个packet数据包的时候不需要携带这个信息。
    // 而在服务端收到一个packet的数据包的时候在转发的时候会将客户端的信息放入这个结构然后发送给需要转发的客户端。所以在客户端收到转发的packet数据包的时候可以通过这个sock结构得知发送的客户端的端口信息。在sock结构有用的时候flags中的socket0x0040标识也会被置位。
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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数据包的来源ipdst_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地址
  3. 消息体序列化dst_ip,大端序表示uint32的目的ip地址
  4. 如果消息头的socket置位了接下来就是序列化sock信息
    1. 一个字节的family
    2. 2个字节大端序的uint16)端口号
      1. 如果family是2表示是ipv4则接下来是4个字节的ipv4
      2. 如果family是10表示是ipv6目前不会有这种情况只是为了兼容以后服务端也支持ipv6的情况则接下来是16个字节的ipv6地址
  5. 如果消息头的v6_info置位了则接下来就是ipv6信息
    1. 2个字节大端序的uint16ipv6端口号
    2. 16个字节的ipv6地址
  6. 之后是network_pass加密后的二进制tun接口的流量数据。

4.3. Register数据包

registerpc为3客户端向另一个客户端发送打洞请求的数据包。在完全不知情的情况下当一个客户端需要向另一个客户端发送数据的时候tun接口的流量发送端首先查看自己是否知道对方的地址此时不知道发送端就向服务端发起一个QueryInfo的数据包同时发送端的tun数据包首先通过服务端中转到对端客户端一方面发送端在收到QueryInfo的返回PeerInfo数据包的时候会向接收端发起一个或者一系列Register数据包另一方面接收端在收到Packet数据包之后也会发起一个或者一系列Register数据包。两边同时发起Register进行打洞当有一个数据包能被正确接收则马上返回RegisterACK数据包两边打洞成功。在json格式如下

{
    "cookie": $uint32,
    // 源tun ip
    "src_ip": $uint32,
    // 目的tun ip
    "dst_ip": $uint32,
    // supernode转发时开到的发送者的外网ip信息,发送的时候,不需要填写
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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,结构如下:

{
    // 匹配Register
    "cookie": $uint32,
    // 源tun ip
    "src_ip": $uint32,
    // 目的tun ip
    "dst_ip": &uint32,
    // 发送者的外网ip信息
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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公钥加密

{
    // 匹配RegisterSuper
    "cookie": $uint32,

    // 服务端看来的该客户端的外网ip信息
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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加密

{
    // 来自registerSuper消息的cookie
    "cookie": $uint32,
}

4.7. RegisterSuperNAK数据包

服务端在节点发送RegisterSuper时候如果该数据包里面的初始验证信息等无效则会直接返回RegisterSuperNAK消息此时客户端应该直接退出。

量外如果服务端判断到比如一个用户最多可以创建10个客户端但是这个节点是第11个了在收到该客户端的RegisterSuper以后和会直接返回RegisterSuperNAK消息客户端同样应该直接退出。

RegisterSuperNAK结构如下该结构没有用aes加密

{
    // 来自registerSuper消息的cookie
    "cookie": $uint32,
}

4.8. UnregisterSuper数据包

当客户端没有收到RegisterSuperNAK的情况下没有被服务端拒绝接入在客户端结束之前可以发送UnregisterSuper数据包该数据包告诉服务端客户端将要下线了可以将这个客户端的相关信息删除了。

UnregisterSuper数据包结构如下

{
    // 客户端被分配的ip如果没有分配到则设置为0
    "src_ip": $uint32
}

4.9. QueryInfo数据包

在发送端发送数据包PACKET消息的时候如果对方ip找不到没有打洞则会发起一个QueryInfo数据包服务端在收到相应信息之后会返回锁查找的接收端的信息PEERINFO数据包发送端收到PEERINFO之后便发起Register进行打洞。

结构如下:

{
    // 自身tun ip
    "src_ip": $uint32,
    // 需要查找的目标的tun ip
    "dst_ip": $uint32,
}

4.10. PeerInfo数据包

PeerInfo结构如下

{
    // 标识为暂时没有用都填写0
    "flag": $uint16,
    // 之前发起短的tun ip
    "src_ip": $uint32,
    // 目标的tun ip
    "dst_ip": $uint32,

    // 需要查找的对端的外网通信信息
    "sock": {
        // family表示客户端的ip协议版本2表示ipv410表示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]
    }

}