TURN(Traversal Using Relays around NAT)是 WebRTC 中用于在直接 P2P 连接失败时,通过中继服务器转发音视频数据的协议,是保障复杂网络下通信可用性的"最后防线"。webrtc的TURN 实现严格遵循 RFC 5766 (TURN) 标准,提供了完整的中继、权限控制和通道绑定功能。
一、TurnServerConnection 类
这个轻量级结构体封装了客户端与 TURN 服务器之间的单个传输层连接信息。
cpp
class TurnServerConnection {
public:
TurnServerConnection() : proto_(PROTO_UDP), socket_(NULL) {}
TurnServerConnection(const rtc::SocketAddress& src,
ProtocolType proto,
rtc::AsyncPacketSocket* socket);
const rtc::SocketAddress& src() const { return src_; }
rtc::AsyncPacketSocket* socket() { return socket_; }
bool operator==(const TurnServerConnection& t) const;
bool operator<(const TurnServerConnection& t) const;
std::string ToString() const;
private:
rtc::SocketAddress src_;
rtc::SocketAddress dst_;
cricket::ProtocolType proto_;
rtc::AsyncPacketSocket* socket_;
};
• 用途:在服务器内部唯一标识一个客户端会话。由于 TURN 支持 UDP、TCP 和 TLS,且同一个 IP 可能建立多个连接,因此需要结合源地址、协议类型和 Socket 指针来唯一区分。
• 关键成员:
• src_: 客户端的SocketAddress (IP:Port)。
• proto_: 使用的传输协议 (UDP/TCP/TLS)。
• socket_: 指向底层 AsyncPacketSocket 的指针,用于发送响应。
• 作用:作为 std::map 的 Key,用于在 TurnServer 中查找对应的 TurnServerAllocation。
二、 TurnServerAllocation类
这是 TURN 协议的核心状态机对象。每个成功的 "Allocate" 请求都会创建一个 TurnServerAllocation 实例。它代表了服务器为某个客户端分配的一个中继端口(Relayed Transport Address)。
cpp
class TurnServerAllocation : public rtc::MessageHandler,
public sigslot::has_slots<> {
public:
TurnServerAllocation(TurnServer* server_,
rtc::Thread* thread,
const TurnServerConnection& conn,
rtc::AsyncPacketSocket* server_socket,
const std::string& key);
~TurnServerAllocation() override;
TurnServerConnection* conn() { return &conn_; }
const std::string& key() const { return key_; }
const std::string& transaction_id() const { return transaction_id_; }
const std::string& username() const { return username_; }
const std::string& origin() const { return origin_; }
const std::string& last_nonce() const { return last_nonce_; }
void set_last_nonce(const std::string& nonce) { last_nonce_ = nonce; }
std::string ToString() const;
void HandleTurnMessage(const TurnMessage* msg);
void HandleChannelData(const char* data, size_t size);
sigslot::signal1<TurnServerAllocation*> SignalDestroyed;
private:
class Channel;
class Permission;
typedef std::list<Permission*> PermissionList;
typedef std::list<Channel*> ChannelList;
void HandleAllocateRequest(const TurnMessage* msg);
void HandleRefreshRequest(const TurnMessage* msg);
void HandleSendIndication(const TurnMessage* msg);
void HandleCreatePermissionRequest(const TurnMessage* msg);
void HandleChannelBindRequest(const TurnMessage* msg);
void OnExternalPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& addr,
const int64_t& packet_time_us);
static int ComputeLifetime(const TurnMessage* msg);
bool HasPermission(const rtc::IPAddress& addr);
void AddPermission(const rtc::IPAddress& addr);
Permission* FindPermission(const rtc::IPAddress& addr) const;
Channel* FindChannel(int channel_id) const;
Channel* FindChannel(const rtc::SocketAddress& addr) const;
void SendResponse(TurnMessage* msg);
void SendBadRequestResponse(const TurnMessage* req);
void SendErrorResponse(const TurnMessage* req,
int code,
const std::string& reason);
void SendExternal(const void* data,
size_t size,
const rtc::SocketAddress& peer);
void OnPermissionDestroyed(Permission* perm);
void OnChannelDestroyed(Channel* channel);
void OnMessage(rtc::Message* msg) override;
TurnServer* server_;
rtc::Thread* thread_;
TurnServerConnection conn_;
std::unique_ptr<rtc::AsyncPacketSocket> external_socket_;
std::string key_;
std::string transaction_id_;
std::string username_;
std::string origin_;
std::string last_nonce_;
PermissionList perms_;
ChannelList channels_;
};
2.1 生命周期
• 创建:当收到合法的 Allocate Request 时由 TurnServer 创建。
• 销毁:当生命周期过期(Lifetime expired)、收到 Refresh Request 且寿命设为0、或连接断开时自动销毁,并通过 SignalDestroyed 通知服务器清理资源。
2.2 核心职责
- 处理 TURN 消息 (HandleTurnMessage):
• HandleAllocateRequest: 初始化分配,创建外部 Socket(中继端口)。
• HandleRefreshRequest: 刷新分配的生命周期。
• HandleCreatePermissionRequest: 添加"权限"(Permission)。TURN 安全模型要求客户端必须显式授权某些对端 IP 才能向其发送数据,防止服务器被用作开放代理进行 DDoS 攻击。
• HandleChannelBindRequest: 绑定"通道"(Channel)。为了减少头部开销,TURN 允许将特定的对端 IP 映射到一个短整数 Channel ID,后续数据可以使用更紧凑的 Channel Data 格式传输,而不是完整的 TURN Indication。
• HandleSendIndication: 处理客户端发往对端的数据(Data Indication)。
- 处理数据转发:
• OnExternalPacket: 当外部对端(Peer)向中继端口发送数据时触发。检查是否有对应的 Permission 或 Channel 绑定,如果有,则封装成 TURN Data Indication 或 Channel Data 发回给客户端。
• SendExternal: 将客户端发送的数据转发给外部对端。
- 管理状态:
• perms_: 权限列表(允许哪些 IP 发送数据进来)。
• channels_: 通道绑定列表(IP 到 Channel ID 的映射)。
• external_socket_: 服务器端用于和对端通信的 UDP Socket(即中继地址)。
三、TurnAuthInterface类
TURN 使用基于长期凭证或短期凭证的认证机制(通常是 STUN 的 long-term credential mechanism)。
-
职责:提供回调接口,让服务器获取用户的密钥。
-
关键方法:GetKey(username, realm, key)。
• 服务器调用此方法获取 HA1 = MD5(username:realm:password)。
• 实现者可以是内存数据库、LDAP 查询或简单的硬编码映射。
• 返回 false 表示用户不存在或无效。
cpp
class TurnAuthInterface {
public:
// Gets HA1 for the specified user and realm.
// HA1 = MD5(A1) = MD5(username:realm:password).
// Return true if the given username and realm are valid, or false if not.
virtual bool GetKey(const std::string& username,
const std::string& realm,
std::string* key) = 0;
virtual ~TurnAuthInterface() = default;
};
四、TurnRedirectInterface类
• 职责:允许服务器决定是否将客户端重定向到另一个 TURN 服务器。
• 场景:负载均衡或服务器维护时,服务器可以返回 300 (Try Alternate) 错误,并在 Alternate-Server 属性中指定新的服务器地址。
• 关键方法:ShouldRedirect(address, out)。如果返回 true,out 参数将被填充为重定向的目标地址。
cpp
class TurnRedirectInterface {
public:
virtual bool ShouldRedirect(const rtc::SocketAddress& address,
rtc::SocketAddress* out) = 0;
virtual ~TurnRedirectInterface() {}
};
五、StunMessageObserver类
• 职责:这是一个可选的钩子,用于接收所有经过服务器的原始 STUN/TURN 消息和数据包。
• 用途:主要用于测试、日志记录或网络分析工具,不参与核心业务逻辑。
cpp
class StunMessageObserver {
public:
virtual void ReceivedMessage(const TurnMessage* msg) = 0;
virtual void ReceivedChannelData(const char* data, size_t size) = 0;
virtual ~StunMessageObserver() {}
};
六、TurnServer 类(核心)
该类是TURN 服务器主控制器,是整个 TURN 服务的入口点和协调者。它监听传入的连接,管理所有的 Allocation,并处理路由。
6.1 核心工作流程
-
监听:通过 AddInternalSocket 或 AddInternalServerSocket 绑定本地端口,监听客户端连接。
-
接收数据包 (OnInternalPacket):
• 解析收到的字节流为 STUN 消息。
• 验证消息完整性(Fingerprint)和认证(Message-Integrity)。
• 根据事务 ID 或连接上下文找到对应的 TurnServerConnection。
- 路由消息:
• 如果是 Allocate 请求且未找到 Allocation -> 调用 CreateAllocation。
• 如果已存在 Allocation -> 转发给 TurnServerAllocation::HandleTurnMessage。
• 如果是 Binding Request (STUN) -> 直接回复,用于保活或探测。
- 管理 Allocation (allocations_):
• 维护一个 Map:TurnServerConnection -> TurnServerAllocation。
• 当 Allocation 销毁时 (OnAllocationDestroyed),从 Map 中移除并清理资源。
- 外部 Socket 工厂:
• SetExternalSocketFactory:设置用于创建中继端口(对外通信 Socket)的工厂。这允许服务器配置中继 IP 地址池。
6.2 关键配置
• realm_: 认证域,用于生成 Nonce。
• auth_hook_: 认证回调。
• enable_otu_nonce_: 是否启用一次性 Nonce(增强安全性,防止重放攻击)。
• reject_private_addresses_: 是否拒绝客户端创建指向私有 IP(RFC1918)的权限(安全策略,防止内网扫描)
七、工作流程
-
客户端发送 Allocate Request 给 TurnServer。
-
TurnServer 验证认证(调用 TurnAuthInterface),检查 Nonce。
-
TurnServer 创建一个新的 TurnServerAllocation 对象。
-
TurnServerAllocation 创建一个外部 UDP Socket(中继端口),并监听该端口。
-
TurnServerAllocation 回复 Allocate Success Response,告知客户端其中继地址和端口。
-
客户端发送 CreatePermission Request 指定对端 IP。
-
TurnServerAllocation 将该 IP 加入 perms_ 列表。
-
客户端发送 Send Indication 包含数据和目标 IP。
-
TurnServerAllocation 检查权限,通过外部 Socket 将数据发给对端。
-
对端回复数据到中继端口。
-
TurnServerAllocation 的 OnExternalPacket 被触发,检查权限/通道,将数据封装后发回给客户端。