CI/CD / Build Frontend (push) Failing after 11s
CI/CD / Test (macos-latest) (push) Has been cancelled
CI/CD / Test (windows-latest) (push) Has been cancelled
CI/CD / Build Desktop (linux) (push) Has been cancelled
CI/CD / Build Desktop (macos) (push) Has been cancelled
CI/CD / Build Desktop (windows) (push) Has been cancelled
CI/CD / Release (push) Has been cancelled
CI/CD / Test (ubuntu-latest) (push) Failing after 2s
- tscore: Protocol implementation (packets, crypto, connection handshake) - tsaudio: Audio engine (capture, playback, codec, VAD, jitter buffer) - tsdb: SQLite database (identities, bookmarks, messages, settings) - shared: Core types and events - tauri-app: Tauri v2 desktop application with React frontend - docs: SRS, SAD, SDD documentation - CI/CD: GitHub Actions workflow - 32 unit tests passing
10 KiB
10 KiB
TeamSpeak 3 协议分析
概述
TeamSpeak 3 使用基于 UDP 的自定义协议进行通信。协议设计支持加密、压缩和可靠传输。
协议层次
应用层 (Commands/Notifications)
↓
消息层 (Messages)
↓
数据包层 (Packets)
↓
传输层 (UDP)
1. 数据包结构
1.1 数据包头
客户端 → 服务器:
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+
| MAC | PId | CId |PT| Data |
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//----------+
| \ Meta |
\ Header /
服务器 → 客户端:
+--+--+--+--+--+--+--+--+--+--+--+------------//-------------+
| MAC | PId |PT| Data |
+--+--+--+--+--+--+--+--+--+--+--+------------//-------------+
| \ Meta |
\ Header /
| 字段 | 大小 | 类型 | 说明 |
|---|---|---|---|
| MAC | 8 字节 | [u8] | EAX 消息认证码 |
| PId | 2 字节 | u16 | 数据包 ID |
| CId | 2 字节 | u16 | 客户端 ID (仅客户端→服务器) |
| PT | 1 字节 | u8 | 数据包类型 + 标志 |
| Data | ≤487/489 字节 | [u8] | 数据包负载 |
1.2 数据包类型
| 类型 | 值 | 说明 |
|---|---|---|
| Voice | 0x00 | 语音数据 |
| VoiceWhisper | 0x01 | 私语数据 |
| Command | 0x02 | 命令数据 |
| CommandLow | 0x03 | 低优先级命令 |
| Ping | 0x04 | Ping 包 |
| Pong | 0x05 | Pong 包 |
| Ack | 0x06 | 确认包 |
| AckLow | 0x07 | 低优先级确认 |
| Init1 | 0x08 | 初始化包 |
1.3 标志位
MSB LSB
+--+--+--+--+--+--+--+--+
|UE|CP|NP|FR| Type |
+--+--+--+--+--+--+--+--+
| 标志 | 位 | 说明 |
|---|---|---|
| UE | 0x80 | 未加密 |
| CP | 0x40 | 已压缩 |
| NP | 0x20 | 新协议 |
| FR | 0x10 | 已分片 |
2. 初始化握手
2.1 低级握手 (Init1)
握手过程包含 5 个步骤:
-
Packet 0 (客户端→服务器):
- 客户端版本 (4 字节时间戳)
- 步骤号: 0x00
- 当前时间戳 (4 字节)
- 随机数 A0 (4 字节)
- 保留字节 (8 字节)
-
Packet 1 (服务器→客户端):
- 步骤号: 0x01
- 服务器数据 A1 (16 字节)
- A0 反转 (4 字节)
-
Packet 2 (客户端→服务器):
- 客户端版本
- 步骤号: 0x02
- A1 (16 字节)
- A0r (4 字节)
-
Packet 3 (服务器→客户端):
- 步骤号: 0x03
- x (64 字节)
- n (64 字节)
- level (4 字节)
- 服务器数据 A2 (100 字节)
-
Packet 4 (客户端→服务器):
- 客户端版本
- 步骤号: 0x04
- x, n, level
- A2 (100 字节)
- y = x^(2^level) mod n (64 字节)
- clientinitiv 命令
2.2 高级握手
2.2.1 clientinitiv (客户端→服务器)
clientinitiv alpha={alpha} omega={omega} ot={ot} ip={ip}
alpha: 10 随机字节的 base64 编码omega: ASN.1-DER 编码的 ECDH 公钥ot: 固定为 1ip: 服务器 IP 地址
2.2.2 initivexpand/initivexpand2 (服务器→客户端)
旧协议 (<3.1):
initivexpand alpha={alpha} beta={beta} omega={omega}
新协议 (≥3.1):
initivexpand2 l={l} beta={beta} omega={omega} ot={ot} proof={proof} tvd={tvd}
2.2.3 共享密钥计算
旧协议:
sharedSecret = ECDH(serverPublicKey, ownPrivateKey)
x = sharedSecret.x.AsByteArray()
sharedData = x[0..32] (填充到 32 字节)
SharedIV = sha1(sharedData)
SharedIV[0..10] ^= alpha.decode64()
SharedIV[10..20] ^= beta.decode64()
SharedMac = sha1(SharedIV)[0..8]
新协议:
// 处理许可证链
next_key = public_key * clamp(sha512(block[1..])[0..32]) + parent
// 计算共享密钥
sharedData = next_key * client_private_key
SharedIV = sha512(sharedData[0..32])
SharedIV[0..10] ^= alpha.decode64()
SharedIV[10..64] ^= beta.decode64()
SharedMac = sha1(SharedIV)[0..8]
3. 加密机制
3.1 EAX 模式加密
使用 AES-128-CTR + OMAC (EAX 模式) 加密数据包。
加密参数生成:
temporary[0] = 0x30 (服务器→客户端) 或 0x31 (客户端→服务器)
temporary[1] = PT
temporary[2..6] = PGId (网络字节序)
temporary[6..26] = SIV[0..20] (旧协议) 或 temporary[6..70] = SIV[0..64] (新协议)
keynonce = sha256(temporary)
key = keynonce[0..16]
nonce = keynonce[16..32]
key[0] ^= ((PId & 0xFF00) >> 8)
key[1] ^= ((PId & 0x00FF) >> 0)
3.2 数据包栈
发送 接收
+-----------+ +-----------+
| Data | | Λ | Data |
+-----------+ | | +-----------+
| Compress | | | | Decompress|
+-----------+ | | +-----------+
| Split | | | | Merge |
+-----------+ | | +-----------+
| Encrypt | V | | Decrypt |
+-----------+ +-----------+
4. 命令协议
4.1 命令格式
命令使用类似 TeamSpeak 3 Query 的文本格式,UTF-8 编码。
示例:
clientinit client_nickname=MyName client_version=3.0.19.3 client_platform=Windows ...
4.2 消息类型
- 请求-响应: 客户端发送命令,服务器返回响应
- 通知: 服务器主动推送的事件通知
- 列表: 分批返回的列表数据
4.3 关键命令
客户端初始化
clientinit client_nickname client_version client_platform client_input_hardware client_output_hardware client_default_channel client_default_channel_password client_server_password client_meta_data client_version_sign client_key_offset client_nickname_phonetic client_default_token hwid
服务器初始化响应
initserver server_name server_platform server_version ...
频道列表
channellist -topic -flags -limits
channellistfinished
客户端进入视图
notifycliententerview cfid ctid reasonid clid client_database_id client_nickname ...
5. 数据结构
5.1 客户端数据
| 字段 | 类型 | 说明 |
|---|---|---|
| client_id | ClientId | 客户端 ID |
| client_database_id | ClientDbId | 数据库 ID |
| client_unique_identifier | Uid | 唯一标识符 |
| client_nickname | str | 昵称 |
| client_type | ClientType | 客户端类型 |
| client_servergroups | ServerGroupId | 服务器组 |
| client_channel_group_id | ChannelGroupId | 频道组 |
| client_away | bool | 是否离开 |
| client_away_message | str | 离开消息 |
| client_talk_power | i32 | 说话权限 |
| client_is_talker | bool | 是否有说话权 |
| client_is_priority_speaker | bool | 是否优先发言者 |
| client_is_channel_commander | bool | 是否频道指挥官 |
| client_is_recording | bool | 是否录音中 |
| client_input_muted | bool | 输入是否静音 |
| client_output_muted | bool | 输出是否静音 |
5.2 频道数据
| 字段 | 类型 | 说明 |
|---|---|---|
| channel_id | ChannelId | 频道 ID |
| channel_name | str | 频道名称 |
| channel_topic | str | 频道主题 |
| channel_codec | Codec | 编解码器 |
| channel_codec_quality | u8 | 编解码器质量 |
| channel_maxclients | i32 | 最大客户端数 |
| channel_maxfamilyclients | i32 | 最大系列客户端数 |
| channel_flag_permanent | bool | 是否永久频道 |
| channel_flag_semi_permanent | bool | 是否半永久频道 |
| channel_flag_default | bool | 是否默认频道 |
| channel_flag_password | bool | 是否有密码 |
| channel_needed_talk_power | i32 | 所需说话权限 |
| channel_forced_silence | bool | 是否强制静音 |
5.3 服务器数据
| 字段 | 类型 | 说明 |
|---|---|---|
| virtualserver_name | str | 服务器名称 |
| virtualserver_platform | str | 平台 |
| virtualserver_version | str | 版本 |
| virtualserver_maxclients | u16 | 最大客户端数 |
| virtualserver_clientsonline | u16 | 在线客户端数 |
| virtualserver_channelsonline | u64 | 频道数 |
| virtualserver_uptime | DurationSeconds | 运行时间 |
| virtualserver_codec_encryption_mode | CodecEncryptionMode | 编解码器加密模式 |
| virtualserver_hostmessage | str | 主机消息 |
| virtualserver_hostmessage_mode | HostMessageMode | 主机消息模式 |
6. 错误处理
6.1 错误码格式
错误响应格式:
error id=0 msg=ok
6.2 常见错误码
| 错误码 | 名称 | 说明 |
|---|---|---|
| 0x0000 | ok | 成功 |
| 0x0200 | client_invalid_id | 无效客户端 ID |
| 0x0201 | client_nickname_inuse | 昵称已被使用 |
| 0x0208 | client_invalid_password | 无效密码 |
| 0x0300 | channel_invalid_id | 无效频道 ID |
| 0x0400 | server_invalid_id | 无效服务器 ID |
| 0x0403 | server_maxclients_reached | 服务器已满 |
7. 版本兼容性
7.1 协议版本
- 旧协议: <3.1,使用 initivexpand
- 新协议: ≥3.1,使用 initivexpand2,支持许可证验证
7.2 客户端版本
客户端版本使用时间戳格式:
3.0.19.3 [Build: 1466672534]
版本时间戳计算:
version_timestamp = unix_timestamp - 1356998400
8. 安全机制
8.1 RSA 拼图
防止 DoS 攻击的机制:
- 服务器发送 RSA 模数 n、底数 x 和难度 level
- 客户端计算 y = x^(2^level) mod n
- 计算时间随 level 指数增长
8.2 Hashcash
身份验证的 hashcash 机制:
data = sha1(publicKey + keyOffset)
level = count_leading_zero_bits(data)
8.3 身份识别
客户端唯一标识符计算:
uid = base64(sha1(publicKey))
9. 语音传输
9.1 编解码器
| 编解码器 | 说明 |
|---|---|
| SpeexNarrowband | 8kHz, 单声道 |
| SpeexWideband | 16kHz, 单声道 |
| SpeexUltrawideband | 32kHz, 单声道 |
| CeltMono | 48kHz, 单声道 |
| OpusVoice | 48kHz, 单声道, 语音优化 |
| OpusMusic | 48kHz, 立体声, 音乐优化 |
9.2 语音包格式
客户端→服务器:
+--+--+--+---------//---------+
| VId |C | Data |
+--+--+--+---------//---------+
服务器→客户端:
+--+--+--+--+--+---------//---------+
| VId | CId |C | Data |
+--+--+--+--+--+---------//---------+
9.3 私语
支持两种私语模式:
- 直接目标: 指定客户端/频道 ID
- 组目标: 指定服务器组/频道组
10. 连接管理
10.1 心跳
- 服务器定期发送 Ping 包
- 客户端必须回复 Pong 包
- 客户端也应主动发送 Ping 包
10.2 可靠传输
- 使用选择性重传机制
- 超时后重传数据包
- 30 秒无响应则断开连接
- 使用指数退避算法避免网络拥塞
10.3 数据包 ID 管理
- 每种数据包类型和方向有独立的 ID 计数器
- 客户端有 9 个出站计数器
- ID 从 1 开始,溢出时增加代计数器
- 代计数器用于加密参数生成