punchnet-android/Protocol.md
2026-06-20 13:28:22 +08:00

26 KiB
Raw Blame History

SDLAN 协议说明

本文档描述 Client 端和当前服务端的交互协议。当前控制面支持两种接入方式:

  • QUIC 接入:实现位于 src/quic,主入口为 sdlan_quic_serversdlan_quic_transport
  • SSL/TLS 接入:实现位于 src/ssl,主入口为 sdlan_ssl_serversdlan_ssl_transport

两种接入方式在传输层不同,但进入 sdlan_session 后使用同一套应用层包类型和 protobuf 消息。Client 完成控制面注册后,还需要通过 UDP STUN 服务上报 NAT 映射并承载节点之间的数据转发。

1. 加密说明

服务端在网络启动时为每个网络生成独立密钥Client 通过 RegisterSuper 响应拿到当前网络的加密参数。

algorithm:
  aes       AES-256
  chacha20  ChaCha20

key:
  长度 32 字节。
  RegisterSuperAck.key 会使用客户端 RegisterSuper.pub_key 中的 RSA 公钥加密。

AES 参数:
  blockMode: cbc
  padding: pkcs7Padding
  iv: key 的前 16 字节

ChaCha20 参数:
  key: RegisterSuperAck.key 解密后的 32 字节密钥
  region_id: RegisterSuperAck.region_id用于客户端侧逻辑分区/nonce 生成

SDLData.data 字段承载隧道中的原始二层/三层数据Client 需要按 RegisterSuperAck.algorithmkey 做加解密;包头中的 network_idsrc_macdst_macttlsession_tokenidentity_id 不参与该字段加密。

2. 通用应用层帧

QUIC 和 SSL/TLS 接入最终交给 sdlan_session:handle_frame/2 的应用层帧格式一致:

Frame = <<PacketType:8, ProtobufPayload/binary>>
  • PacketType1 字节包类型,取值见第 3 节。
  • ProtobufPayloadprotobuf 编码后的消息体。没有消息体的包只包含 PacketType
  • Event 和 Command 当前已经改为 protobuf oneof 结构,不再使用旧文档中的二级编码字节。

3. 包类型定义

包类型以 include/sdlan.hrl 为准。

方向 消息 说明
PACKET_EMPTY 0x00 双向 空包,当前主流程未使用。
PACKET_REGISTER_SUPER 0x01 Client -> Server SDLRegisterSuper 控制连接注册,必须在收到 Welcome 后发送。
PACKET_REGISTER_SUPER_ACK 0x02 Server -> Client SDLRegisterSuperAck 注册成功,返回网络加密参数和 session_token
PACKET_REGISTER_SUPER_NAK 0x04 Server -> Client SDLRegisterSuperNak 注册失败,返回错误码和错误消息,随后连接关闭。
PACKET_UNREGISTER 0x05 Client -> Server 主动注销当前控制连接,服务端清理网络绑定后关闭连接。
PACKET_QUERY_INFO 0x06 Client -> Server SDLQueryInfo 查询目标 MAC 的 NAT/IPv6 信息,用于发起 P2P 打洞。
PACKET_PEER_INFO 0x07 Server -> Client SDLPeerInfo QUERY_INFO 的响应。找不到目标时返回空 v4_info/v6_info
PACKET_PING 0x08 Client -> Server 控制连接心跳。
PACKET_PONG 0x09 Server -> Client 心跳响应。
PACKET_EVENT 0x10 Server -> Client SDLEvent 服务端主动事件推送Client 不需要 Ack。
PACKET_COMMAND 0x11 Server -> Client SDLCommand 服务端主动命令推送,需要 Client 回 COMMAND_ACK
PACKET_COMMAND_ACK 0x12 Client -> Server SDLCommandAck 命令处理结果,pkt_id 必须等于 SDLCommand.pkt_id
PACKET_FLOW_TRACER 0x15 Client -> Server 历史保留 sdlan_session 当前未处理该包。
PACKET_REGISTER 0x20 Client <-> Client SDLRegister 节点之间 UDP 打洞握手请求。
PACKET_REGISTER_ACK 0x21 Client <-> Client SDLRegisterAck 节点之间 UDP 打洞握手响应。
PACKET_STUN_REQUEST 0x30 Client -> STUN SDLStunRequest 周期上报 NAT 映射和 IPv6 信息,同时维持 NAT 映射。
PACKET_STUN_REPLY 0x31 STUN -> Client SDLStunReply STUN 心跳响应。
PACKET_STUN_PROBE 0x32 Client -> STUN SDLStunProbe NAT 类型探测请求。
PACKET_STUN_PROBE_REPLY 0x33 STUN -> Client SDLStunProbeReply NAT 类型探测响应,返回服务端看到的公网 IP/端口。
PACKET_STUN_PROBE_RELAY 0x3a STUN 内部 内部二进制 STUN 辅助节点内部转发探测响应Client 不直接使用。
PACKET_WELCOME 0x4f Server -> Client SDLWelcome 控制连接建立后服务端首先下发的欢迎包。
PACKET_ARP_REQUEST 0x50 Client -> Server SDLArpRequest 查询虚拟网络 IP 对应的 MAC。
PACKET_ARP_RESPONSE 0x51 Server -> Client SDLArpResponse ARP 查询响应。
PACKET_POLICY_REQUEST 0xb0 Client -> Server SDLPolicyRequest 查询源身份到目标身份的访问规则。
PACKET_POLICY_REPLY 0xb1 Server -> Client SDLPolicyResponse 权限规则响应。
PACKET_EXPOSED_SERVICE_REQUEST 0xb2 Client -> Server SDLExposedServiceRequest 查询当前节点暴露服务端口列表。
PACKET_EXPOSED_SERVICE_RESPONSE 0xb3 Server -> Client SDLExposedServiceResponse 暴露服务端口响应。
PACKET_STUN_DATA 0xff Client <-> STUN/Client SDLData UDP 数据包,支持服务端转发和 P2P 直连。

STUN 探测属性同样定义在 include/sdlan.hrl

STUN_ATTR_CHANGE_NONE = 0  使用收到请求的同一个 socket 响应
STUN_ATTR_CHANGE_PORT = 1  通过 stun_peer_assist 路径转发响应,通常用于测试变化 IP/端口路径
STUN_ATTR_CHANGE_PEER = 2  通过 stun_port_assist 路径响应,通常用于测试变化端口路径

4. Protobuf 消息

protobuf 定义以 proto/sdlan.proto 为准Erlang 生成文件为 src/sdlan_pb.erlinclude/sdlan_pb.hrl

4.1 公共类型

message SDLV4Info {
    uint32 port = 1;
    bytes v4 = 2;
    uint32 nat_type = 3;
}

message SDLV6Info {
    uint32 port = 1;
    bytes v6 = 2;
}
  • SDLV4Info.port:服务端观察到的客户端 NAT 端口。
  • SDLV4Info.v44 字节 IPv4 地址,网络字节序。
  • SDLV4Info.nat_typeClient 上报的 NAT 类型。
  • SDLV6Info.portIPv6 辅助通道端口。
  • SDLV6Info.v616 字节 IPv6 地址。

4.2 NAT 类型约定

0 blocked         网络不可达
1 noNat           当前设备在公网地址下
2 fullCone        Full Cone NAT
3 portRestricted  Port Restricted Cone NAT
4 coneRestricted  Restricted Cone NAT
5 symmetric       Symmetric NAT

5. QUIC 接入

QUIC 接入代码位于 src/quic。服务端监听配置来自 quic_server

  • 默认端口:443
  • ALPNpunchnet/1.0
  • 服务端允许 1 条双向 streampeer_bidi_stream_count => 1
  • 默认最大应用包:max_packet_size = 16384
  • 默认心跳间隔:heartbeat_sec = 15

5.1 QUIC 传输帧

QUIC 在单条双向 stream 上承载控制面消息。stream 上的实际传输格式为:

QuicStreamData = <<Len:16, Frame:Len/binary>>
Frame          = <<PacketType:8, ProtobufPayload/binary>>
  • Len2 字节无符号长度,表示后续 Frame 字节数,不包含 Len 自身。
  • Frame:第 2 节定义的通用应用层帧。
  • 服务端接收时支持一次 QUIC data 中包含多个完整 frame也支持半包累积。
  • Len > max_packet_size 时,服务端关闭连接,原因是 frame_too_large

5.2 QUIC 连接建立流程

  1. Client 与服务端完成 QUIC/TLS 握手ALPN 必须匹配 punchnet/1.0
  2. Client 打开一条双向 stream。
  3. 服务端接受第一条 stream 后立即发送 PACKET_WELCOME
  4. Client 收到 Welcome 后发送 PACKET_REGISTER_SUPER
  5. 注册成功后服务端返回 PACKET_REGISTER_SUPER_ACK 并进入 registered 状态。
  6. 注册失败时服务端返回 PACKET_REGISTER_SUPER_NAK,随后关闭连接。
  7. 进入 registeredClient 必须按 Welcome 中的 heartbeat_sec 周期发送 PACKET_PING

5.3 QUIC Welcome

Server -> Client
<<Len:16, 0x4f:8, SDLWelcome/binary>>
message SDLWelcome {
    uint32 version = 1;
    uint32 max_bidi_streams = 2;
    uint32 max_packet_size = 3;
    uint32 heartbeat_sec = 4;
    SDLV6Info ipv6_assist = 5;
}
  • version:协议版本,当前为 1
  • max_bidi_streams:服务端允许的双向 stream 数,当前为 1
  • max_packet_size:单个应用层 frame 最大长度。
  • heartbeat_sec心跳间隔Client 应以小于或等于该值的周期发送 Ping。
  • ipv6_assistIPv6 辅助器地址。未配置时为空。

5.4 QUIC RegisterSuper

Client -> Server
<<Len:16, 0x01:8, SDLRegisterSuper/binary>>
message SDLRegisterSuper {
    string client_id = 1;
    uint32 network_id = 2;
    bytes mac = 3;
    uint32 ip = 4;
    uint32 mask_len = 5;
    string hostname = 6;
    string pub_key = 7;
    string access_token = 8;
}
  • client_id:客户端节点 ID不能为空。
  • network_id:客户端要加入的网络 ID。
  • mac:虚拟网卡 MAC不能为空且不能是广播或组播 MAC。
  • ipHTTP 接口已分配的虚拟网络 IPv4按 32 位整数传输。
  • mask_len:虚拟网络掩码长度。
  • hostname:客户端主机名,服务端用于维护域名映射。
  • pub_key:客户端 RSA 公钥 PEM服务端用它加密网络密钥。
  • access_token:客户端通过 HTTP 登录或令牌换取的访问凭证,服务端注册时调用 API 校验。

注册成功:

Server -> Client
<<Len:16, 0x02:8, SDLRegisterSuperAck/binary>>
message SDLRegisterSuperAck {
    string algorithm = 1;
    bytes key = 2;
    uint32 region_id = 3;
    bytes session_token = 4;
}
  • algorithm:当前网络加密算法,取值为 aeschacha20
  • key:使用 pub_key 加密后的网络密钥Client 需要用私钥解密。
  • region_id:服务端根据虚拟 IP 生成的逻辑分区 ID。
  • session_token:本次控制会话 token。后续 UDP SDLStunRequestSDLData 必须携带,用于校验端和网络绑定关系。

注册失败:

Server -> Client
<<Len:16, 0x04:8, SDLRegisterSuperNak/binary>>
message SDLRegisterSuperNak {
    uint32 error_code = 1;
    string error_message = 2;
}
  • error_code:错误码。网络/API 错误当前使用 4,服务内部错误当前使用 5API 返回业务错误时透传 API code。
  • error_message:错误描述。服务端发送 NAK 后关闭连接。

5.5 QUIC 心跳

Client -> Server
<<Len:16, 0x08:8>>

Server -> Client
<<Len:16, 0x09:8>>
  • Client 只在注册成功后发送 Ping。
  • 服务端收到 Ping 后立即回复 Pong并记录本轮心跳已收到。
  • 服务端按 heartbeat_sec 检查上一周期是否收到过 Ping如果没有收到关闭连接。

5.6 QUIC 查询 PeerInfo

Client -> Server
<<Len:16, 0x06:8, SDLQueryInfo/binary>>
message SDLQueryInfo {
    bytes dst_mac = 1;
}
  • dst_mac:目标节点 MAC。
  • 服务端查找目标节点的 NAT 映射和 IPv6 信息。
  • 如果目标存在,服务端同时会向目标节点推送 SDLEvent.SendRegister,让目标主动向查询方发送打洞包,提高 P2P 成功率。

响应:

Server -> Client
<<Len:16, 0x07:8, SDLPeerInfo/binary>>
message SDLPeerInfo {
    bytes dst_mac = 1;
    optional SDLV4Info v4_info = 2;
    optional SDLV6Info v6_info = 3;
}
  • dst_mac:原样返回查询的目标 MAC。
  • v4_info:目标节点最近一次 STUN_REQUEST 上报形成的公网 IPv4/端口和 NAT 类型。
  • v6_info:目标节点上报的 IPv6 辅助信息。没有时为空。
  • 找不到目标或目标没有可用 NAT 信息时,服务端仍返回 SDLPeerInfo,但 v4_infov6_info 为空。

5.7 QUIC ARP 查询

Client -> Server
<<Len:16, 0x50:8, SDLArpRequest/binary>>
message SDLArpRequest {
    uint32 target_ip = 1;
    uint32 origin_ip = 2;
    bytes context = 3;
}
  • target_ip:要查询 MAC 的虚拟网络 IPv4。
  • origin_ip:发起查询的虚拟网络 IPv4。
  • contextClient 自定义上下文,服务端原样回写,方便客户端匹配本地 ARP 请求。

响应:

Server -> Client
<<Len:16, 0x51:8, SDLArpResponse/binary>>
message SDLArpResponse {
    uint32 target_ip = 1;
    bytes target_mac = 2;
    uint32 origin_ip = 3;
    bytes context = 4;
}
  • target_mac:查询成功时为目标 MAC查询失败时为空字节串。
  • 其它字段与请求一致或对应请求上下文。

5.8 QUIC Policy 查询

Client -> Server
<<Len:16, 0xb0:8, SDLPolicyRequest/binary>>
message SDLPolicyRequest {
    uint32 src_identity_id = 1;
    uint32 dst_identity_id = 2;
    uint32 version = 3;
}
  • src_identity_id:源端身份 ID。
  • dst_identity_id:目标端身份 ID。
  • versionClient 侧规则版本服务端原样回写Client 用于判断是否覆盖本地缓存。

响应:

Server -> Client
<<Len:16, 0xb1:8, SDLPolicyResponse/binary>>
message SDLPolicyResponse {
    uint32 src_identity_id = 1;
    uint32 dst_identity_id = 2;
    uint32 version = 3;
    bytes rules = 4;
}
  • rules:稀疏序列化规则列表,每条规则格式为 <<Proto:8, Port:16>>
  • Proto:协议号,例如 TCP/UDP 对应的协议编号。
  • Port:允许访问的目标端口。
  • 服务端只下发 allow 规则deny 规则在服务端侧已过滤。
  • 如果请求解析或规则查询失败,当前服务端可能不返回响应包。

5.9 QUIC ExposedService 查询

Client -> Server
<<Len:16, 0xb2:8, SDLExposedServiceRequest/binary>>
message SDLExposedServiceRequest {
    uint32 version = 1;
}
  • versionClient 当前缓存版本,服务端响应中原样返回。

响应:

Server -> Client
<<Len:16, 0xb3:8, SDLExposedServiceResponse/binary>>
message SDLExposedServiceResponse {
    uint32 version = 1;
    repeated uint32 tcp_ports = 2;
    repeated uint32 udp_ports = 3;
}
  • tcp_ports:当前节点允许暴露的 TCP 端口列表。
  • udp_ports:当前节点允许暴露的 UDP 端口列表。
  • 服务端只返回 1..65534 范围内的端口。
  • API 查询失败时当前服务端可能不返回响应包。

5.10 QUIC Event 推送

Server -> Client
<<Len:16, 0x10:8, SDLEvent/binary>>
message SDLEvent {
    message NatChanged {
        bytes mac = 1;
        uint32 ip = 2;
    }

    message SendRegister {
        bytes dst_mac = 1;
        uint32 nat_ip = 2;
        uint32 nat_port = 3;
        uint32 nat_type = 4;
        optional SDLV6Info v6_info = 5;
    }

    message ExposedServiceChanged {
    }

    message NetworkShutdown {
        string message = 1;
    }

    oneof event {
        NatChanged nat_changed = 1;
        SendRegister send_register = 2;
        NetworkShutdown shutdown = 3;
        ExposedServiceChanged exposed_service_changed = 4;
    }
}
  • nat_changed:某个 MAC 的虚拟 IP 或 NAT 映射发生变化。Client 应清理对应 peer 缓存、ARP 缓存或重新查询 peer 信息。
  • send_register:服务端要求当前 Client 向 dst_mac 对应节点发送 UDP PACKET_REGISTER 打洞请求。nat_ip/nat_port/nat_type/v6_info 是目标可达地址信息。
  • exposed_service_changed当前节点暴露服务配置变化。Client 应重新发送 PACKET_EXPOSED_SERVICE_REQUEST 获取端口列表。
  • shutdown网络关闭。Client 应停止当前网络会话并释放本地资源。
  • Event 不需要 Client 回复 Ack。

5.11 QUIC Command 下发和 ACK

Server -> Client
<<Len:16, 0x11:8, SDLCommand/binary>>
message SDLCommand {
    uint32 pkt_id = 1;

    message ExitNodeControl {
        int32 action = 1;
        string remark = 2;
    }

    oneof command {
        ExitNodeControl exit_node = 2;
    }
}
  • pkt_id服务端生成的命令序号Client 回复时必须原样带回。
  • exit_node.action:出口节点控制动作,具体动作值由业务层约定。
  • exit_node.remark:操作备注或调试说明。

Client 处理完成后回复:

Client -> Server
<<Len:16, 0x12:8, SDLCommandAck/binary>>
message SDLCommandAck {
    uint32 pkt_id = 1;
    int32 code = 2;
    string message = 3;
    bytes data = 4;
}
  • pkt_id:必须与收到的 SDLCommand.pkt_id 一致。
  • code:处理结果码,建议 0 表示成功,非 0 表示失败。
  • message:处理结果说明。
  • data:可选业务数据。
  • 服务端收到 ACK 后,会把结果转发给等待该命令响应的内部调用方。

5.12 QUIC Unregister

Client -> Server
<<Len:16, 0x05:8>>
  • Client 主动退出网络时发送。
  • 服务端收到后调用 sdlan_network:unregister/3 清理当前 client_id + mac 的绑定,并关闭控制连接。

6. SSL/TLS 接入

SSL/TLS 接入代码位于 src/ssl。服务端通过 Ranch SSL 监听,配置来自 ssl_server

  • 默认端口:1443
  • ALPNpunchnet/1.0
  • TLS 版本:tlsv1.3tlsv1.2
  • 默认最大应用包:max_packet_size = 16384
  • 默认心跳间隔:heartbeat_sec = 15

6.1 SSL/TLS 传输帧

SSL/TLS 接入使用 Erlang socket {packet, 2} 分包。对非 Erlang Client 来说,线上格式仍然是:

TlsStreamData = <<Len:16, Frame:Len/binary>>
Frame         = <<PacketType:8, ProtobufPayload/binary>>
  • Len2 字节无符号长度,表示后续 Frame 字节数,不包含 Len 自身。
  • 服务端 ssl 层会自动剥离 Len 后把 Frame 交给 sdlan_session
  • 服务端发送时也依赖 {packet, 2} 自动加长度前缀。
  • 单个发送包长度必须不超过 65535,业务上应遵守 Welcome 中的 max_packet_size

6.2 SSL/TLS 连接建立流程

  1. Client 与服务端建立 TCP 连接。
  2. Client 完成 TLS 握手ALPN 使用 punchnet/1.0
  3. 服务端 TLS 握手成功后立即发送 PACKET_WELCOME
  4. Client 收到 Welcome 后发送 PACKET_REGISTER_SUPER
  5. 注册成功后服务端返回 PACKET_REGISTER_SUPER_ACK 并进入 registered 状态。
  6. 注册失败时服务端返回 PACKET_REGISTER_SUPER_NAK,随后关闭连接。
  7. 进入 registeredClient 必须按 Welcome 中的 heartbeat_sec 周期发送 PACKET_PING

6.3 SSL/TLS 消息交互

SSL/TLS 接入的应用层消息与 QUIC 完全一致,区别只在传输层:

  • Welcome5.3,包类型 0x4f
  • RegisterSuper / RegisterSuperAck / RegisterSuperNak5.4,包类型 0x01/0x02/0x04
  • Ping / Pong5.5,包类型 0x08/0x09
  • QueryInfo / PeerInfo5.6,包类型 0x06/0x07
  • ARP 查询:见 5.7,包类型 0x50/0x51
  • Policy 查询:见 5.8,包类型 0xb0/0xb1
  • ExposedService 查询:见 5.9,包类型 0xb2/0xb3
  • Event 推送:见 5.10,包类型 0x10
  • Command / CommandAck5.11,包类型 0x11/0x12
  • Unregister5.12,包类型 0x05

Client 如果同时支持 QUIC 和 SSL/TLS建议抽象出同一个应用层 Frame 编解码模块,仅替换底层连接、握手和 stream/socket 读写逻辑。

7. UDP STUN 和数据转发

UDP 逻辑不属于 src/quicsrc/ssl,但它依赖控制面注册返回的 session_token 和网络密钥,是 Client 完整接入流程的一部分。相关服务端实现位于:

  • src/sdlan_stun.erl
  • src/sdlan_stun_port_assist.erl
  • src/sdlan_stun_peer_assist.erl

UDP 包没有 2 字节长度前缀:

UdpPacket = <<PacketType:8, ProtobufPayload/binary>>

7.1 STUN Request

Client -> STUN
<<0x30:8, SDLStunRequest/binary>>
message SDLStunRequest {
    string client_id = 1;
    uint32 network_id = 2;
    bytes mac = 3;
    uint32 ip = 4;
    uint32 nat_type = 5;
    optional SDLV6Info v6_info = 6;
    bytes session_token = 7;
}
  • client_id/network_id/mac/ip:必须与控制面 RegisterSuper 一致。
  • nat_typeClient 通过 STUN_PROBE 判断出的 NAT 类型。
  • v6_infoClient 可用的 IPv6 辅助信息,没有时为空。
  • session_token:控制面 RegisterSuperAck.session_token,服务端用它校验当前 Client 的 NAT 上报。
  • 服务端以 UDP 源地址和源端口作为该节点的最新 NAT 映射,并通知其它节点 nat_changed

响应:

STUN -> Client
<<0x31:8, SDLStunReply/binary>>
message SDLStunReply {
}
  • 空消息体,仅表示服务端已收到并处理 STUN 心跳。
  • Client 需要周期性发送该包维持 NAT 映射,建议周期小于 NAT 超时时间。

7.2 STUN Probe

Client -> STUN
<<0x32:8, SDLStunProbe/binary>>
message SDLStunProbe {
    uint32 cookie = 1;
    uint32 attr = 2;
    uint32 step = 3;
}
  • cookieClient 生成的随机值,用于匹配响应。
  • attr:探测属性,取值见第 3 节 STUN_ATTR_*
  • stepClient 侧探测步骤编号。当前 sdlan_stun 主响应代码未写回该字段,响应里通常为 protobuf 默认值 0Client 需要兼容。

响应:

STUN -> Client
<<0x33:8, SDLStunProbeReply/binary>>
message SDLStunProbeReply {
    uint32 cookie = 1;
    uint32 step = 2;
    uint32 port = 3;
    uint32 ip = 4;
}
  • cookie:原样返回请求中的 cookie
  • step:当前实现通常为默认值 0
  • port:服务端看到的 Client 公网 UDP 源端口。
  • ip:服务端看到的 Client 公网 IPv4按 32 位整数传输。

典型 NAT 判断流程:

  1. 向主 STUN 地址发送 attr = 0,拿到公网地址 A。
  2. 如果公网地址 A 等于本地 UDP 地址,则为 noNat
  3. 向另一组 STUN 地址发送 attr = 0,拿到公网地址 B。
  4. 如果 A 和 B 的 IP 不同,则倾向判断为 symmetric
  5. 向主 STUN 地址发送 attr = 1,如果能收到辅助 peer 路径响应,则说明 NAT 对来源 IP/端口限制较少。
  6. 向主 STUN 地址发送 attr = 2,如果能收到辅助端口路径响应,则说明 NAT 对来源端口限制较少;如果收不到则按端口限制型或降级策略处理。
  7. 任何关键步骤无响应时,可按 blocked 或降级策略处理。

7.3 Client 间 UDP 打洞

当 Client 通过 QUERY_INFOsend_register 事件拿到对端 NAT 地址后,向对端 UDP 地址发送:

Client A -> Client B
<<0x20:8, SDLRegister/binary>>
message SDLRegister {
    uint32 network_id = 1;
    bytes src_mac = 2;
    bytes dst_mac = 3;
}
  • network_id:当前虚拟网络 ID。
  • src_mac:发送方 MAC。
  • dst_mac:目标方 MAC。
  • 该包用于在双方 NAT 设备上建立映射,不经过控制连接。

对端收到后回复:

Client B -> Client A
<<0x21:8, SDLRegisterAck/binary>>
message SDLRegisterAck {
    uint32 network_id = 1;
    bytes src_mac = 2;
    bytes dst_mac = 3;
}
  • src_macACK 发送方 MAC。
  • dst_macACK 目标方 MAC。
  • 收到 ACK 后Client 可以优先使用 P2P 直连发送 SDLData

7.4 UDP 数据包

Client -> STUN/Peer
<<0xff:8, SDLData/binary>>
message SDLData {
    uint32 network_id = 1;
    bytes src_mac = 2;
    bytes dst_mac = 3;
    bool is_p2p = 4;
    uint32 ttl = 5;
    bytes data = 6;
    bytes session_token = 7;
    uint32 identity_id = 8;
}
  • network_id:当前虚拟网络 ID。
  • src_mac:发送方 MAC。
  • dst_mac:目标 MAC。广播或组播 MAC 会触发服务端向网络内其它在线节点广播转发。
  • is_p2pClient 直连发送时为 true;服务端转发时会改写为 false
  • ttl转发跳数。STUN 服务端转发前会执行 ttl - 1
  • data:加密后的真实业务数据。
  • session_token:控制面注册返回的会话 token。
  • identity_id:源端身份 ID对端用于本地权限判断。

服务端转发逻辑:

  1. STUN 服务端收到 PACKET_STUN_DATA 后解析 SDLData
  2. network_id 找到当前网络 ETS 表。
  3. 校验 src_mac 当前在线且存在 endpoint。
  4. 如果 dst_mac 是广播或组播,转发给除 src_mac 外所有有 NAT hole 的 endpoint。
  5. 如果 dst_mac 是单播,查找目标 endpoint 的 NAT hole 并转发。
  6. 转发前将 ttl 减 1并把 is_p2p 改为 false
  7. 服务端按网络带宽限制做限流;限流或找不到目标时丢弃。

8. 推荐 Client 完整接入流程

  1. 通过 HTTP/API 完成登录和网络/IP 分配,拿到 access_tokennetwork_id、虚拟 IP、掩码、身份 ID 等信息。
  2. 优先尝试 QUIC 接入;如果网络环境不支持 QUIC可降级到 SSL/TLS 接入。
  3. 完成控制连接握手并接收 PACKET_WELCOME
  4. 发送 PACKET_REGISTER_SUPER
  5. 收到 PACKET_REGISTER_SUPER_ACK 后,用私钥解密 key,保存 algorithmregion_idsession_token
  6. 启动控制连接心跳,按 heartbeat_sec 周期发送 PACKET_PING 并处理 PACKET_PONG
  7. 使用 PACKET_STUN_PROBE 判断 NAT 类型。
  8. 周期性发送 PACKET_STUN_REQUEST,上报 NAT 类型、IPv6 信息和 session_token
  9. 有目标通信需求时,先使用 PACKET_ARP_REQUEST 或本地缓存确定目标 MAC。
  10. 通过 PACKET_POLICY_REQUEST 查询访问策略,按 rules 在本地做放行判断。
  11. 通过 PACKET_QUERY_INFO 查询目标 NAT 信息,并根据 SDLEvent.SendRegister 做双向 UDP 打洞。
  12. P2P 打洞成功后优先直连发送 PACKET_STUN_DATA;失败时通过 STUN 服务端转发。
  13. 处理服务端推送的 SDLEventSDLCommand。Command 必须回复 SDLCommandAck
  14. Client 主动退出时发送 PACKET_UNREGISTER 并关闭本地控制连接和 UDP 资源。

===端口说明==== 组件 默认端口/配置 」 说明 」 QUIC | 443 | ALPN punchnet/1.0 |控制面主入口,允许 1条双向 stream。 SSL/TLS | 1443 | ALPN punchnet/1.0 |控制面备用入口,与 QUIC 共用sdlan session STUN | 1365 | acceptor_nums=5 |UDP_NAT 上报、探测和服务端转发 STUN port assist | 1366 |同节点变端 probe reply STUN peer assist |配置的 punchnet.aioe.tech:1366|转发到另一辅助节点后回复。 IPv6 assist | 1367 |IPv6 可达性探测。