# 对等连接

## 协议规范

用 UDP 建立和使用对等连接过程中,也存在特定格式的消息,这些消息全程加密.

### 心跳和握手

心跳和握手使用完全相同的报文格式.

- Type: 用于区分数据包类型,始终为 0
- TUN: 本机虚拟地址
- IP: 本客户端用于对等连接的公网地址
- Port: 本客户端用于对等连接监听的公网端口
- ACK: 0 或 1, 表示是否已经收到了对方的心跳或握手消息,用于更新对等连接状态机

```plaintext
+------------------------------------------------------------------------------+
| Type (1 Byte) | TUN (4 Bytes) | IP (4 Bytes) | Port (2 Bytes) | ACK (1 Byte) |
+------------------------------------------------------------------------------+
```

### 原始 IPv4 报文转发

与通过 WebSocket 转发的报文格式一致.但这是使用的是 UDP 传输.

- Type: 用于区分数据包类型,始终为 1
- Raw: IP 层原始数据

```plaintext
+-------------------------------+
| Type (1 Byte) | Raw (n Bytes) |
+-------------------------------+
```

### 时延探测

向对方发送时延探测包,对方收到后不做处理,直接回复.此时发送方就能够计算出与接收方之间的时延.

- Type: 用于区分数据包类型,始终为 2
- Src: 虚拟源地址
- Dst: 虚拟目的地址
- Timestamp: 本机开机到当前经历的毫秒数

```plaintext
+---------------------------------------------------------------------+
| Type (1 Byte) | Src (4 Bytes) | Dst (4 Bytes) | Timestamp (8 Bytes) |
+---------------------------------------------------------------------+
```

### 路由同步

向所有直连设备对外广播路由变更.

- Type: 用于区分数据包类型,始终为 4
- Dst: 虚拟目的地址
- Next: 虚拟下一跳地址
- Delay: 测得的时延

```plaintext
+------------------------------------------------------------------+
| Type (1 Byte) | Dst (4 Bytes) | Next (4 Bytes) | Delay (8 Bytes) |
+------------------------------------------------------------------+
```

## 状态机

### INIT

当收到服务器通过 WebSocket 转发的 IPv4 报文时,检查源地址是否处于 INIT 状态.如果是,则进入 PREPARING 状态.如果点对点连接功能未启用,则切换到 FAILED 状态。忽略其他状态.

### PREPARING

固定周期检查是否有处于 PREPARING 状态的对端.如果有,则在遍历所有对端后发送 STUN 请求.当收到 STUN 响应时,将其公共网络信息发送给所有处于 PREPARING 状态的对端.如果没有对端的公网信息,则状态切换为 SYNCHRONIZING, 否则切换为 CONNECTING. STUN 是 UDP,可能会丢包，因此只要有处于 PREPARING 状态的对端,它就会不断发送 STUN 请求.

### SYNCHRONIZING

该状态表示已经成功获取了本机的公网信息,但尚未收到对端的公网信息.此时发送不带 ACK 的心跳.对方可能正在发送,或版本不支持,或未启用对等连接.超时后,进入 FAILED 状态.

### CONNECTING

该状态表示自己拥有对端的公网信息,此时发送带 ACK 的心跳.收到对方发送的带有 ACK 的心跳表示连接成功,进入 CONNECTED 状态.超时未成功则进入WAITTING 状态.

### CONNECTED

TUN 接收数据时,如果对端处于 CONNECTED 状态,则直接通过UDP发送.周期性的向对端发送心跳,并检查最近是否收到对端的心跳.如果长时间没有收到对端心跳,则进入 INIT 状态.当 UDP 接收到对等方的心跳时,会重置心跳超时计数.

### WAITING

使用指数退避算法在特定时间后重新进入 INIT 状态.重试时间间隔从 30 秒逐步增大到 1 小时.

### FAILED

FAILED 状态表示对端不支持对等连接,在此状态下的对端不采取任何主动措施.但如果收到对端的连接消息,就会被动进入 PREPARING 状态.这对应着对端从不支持对等连接的客户端切换到支持对等连接的客户端的情况.
