webrtc TURN 主要源码介绍

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 核心职责

  1. 处理 TURN 消息 (HandleTurnMessage):

• HandleAllocateRequest: 初始化分配,创建外部 Socket(中继端口)。

• HandleRefreshRequest: 刷新分配的生命周期。

• HandleCreatePermissionRequest: 添加"权限"(Permission)。TURN 安全模型要求客户端必须显式授权某些对端 IP 才能向其发送数据,防止服务器被用作开放代理进行 DDoS 攻击。

• HandleChannelBindRequest: 绑定"通道"(Channel)。为了减少头部开销,TURN 允许将特定的对端 IP 映射到一个短整数 Channel ID,后续数据可以使用更紧凑的 Channel Data 格式传输,而不是完整的 TURN Indication。

• HandleSendIndication: 处理客户端发往对端的数据(Data Indication)。

  1. 处理数据转发:

• OnExternalPacket: 当外部对端(Peer)向中继端口发送数据时触发。检查是否有对应的 Permission 或 Channel 绑定,如果有,则封装成 TURN Data Indication 或 Channel Data 发回给客户端。

• SendExternal: 将客户端发送的数据转发给外部对端。

  1. 管理状态:

• perms_: 权限列表(允许哪些 IP 发送数据进来)。

• channels_: 通道绑定列表(IP 到 Channel ID 的映射)。

• external_socket_: 服务器端用于和对端通信的 UDP Socket(即中继地址)。

三、TurnAuthInterface类

TURN 使用基于长期凭证或短期凭证的认证机制(通常是 STUN 的 long-term credential mechanism)。

  1. 职责:提供回调接口,让服务器获取用户的密钥。

  2. 关键方法: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 核心工作流程

  1. 监听:通过 AddInternalSocket 或 AddInternalServerSocket 绑定本地端口,监听客户端连接。

  2. 接收数据包 (OnInternalPacket):

• 解析收到的字节流为 STUN 消息。

• 验证消息完整性(Fingerprint)和认证(Message-Integrity)。

• 根据事务 ID 或连接上下文找到对应的 TurnServerConnection。

  1. 路由消息:

• 如果是 Allocate 请求且未找到 Allocation -> 调用 CreateAllocation。

• 如果已存在 Allocation -> 转发给 TurnServerAllocation::HandleTurnMessage。

• 如果是 Binding Request (STUN) -> 直接回复,用于保活或探测。

  1. 管理 Allocation (allocations_):

• 维护一个 Map:TurnServerConnection -> TurnServerAllocation。

• 当 Allocation 销毁时 (OnAllocationDestroyed),从 Map 中移除并清理资源。

  1. 外部 Socket 工厂:

• SetExternalSocketFactory:设置用于创建中继端口(对外通信 Socket)的工厂。这允许服务器配置中继 IP 地址池。

6.2 关键配置

• realm_: 认证域,用于生成 Nonce。

• auth_hook_: 认证回调。

• enable_otu_nonce_: 是否启用一次性 Nonce(增强安全性,防止重放攻击)。

• reject_private_addresses_: 是否拒绝客户端创建指向私有 IP(RFC1918)的权限(安全策略,防止内网扫描)

七、工作流程

  1. 客户端发送 Allocate Request 给 TurnServer。

  2. TurnServer 验证认证(调用 TurnAuthInterface),检查 Nonce。

  3. TurnServer 创建一个新的 TurnServerAllocation 对象。

  4. TurnServerAllocation 创建一个外部 UDP Socket(中继端口),并监听该端口。

  5. TurnServerAllocation 回复 Allocate Success Response,告知客户端其中继地址和端口。

  6. 客户端发送 CreatePermission Request 指定对端 IP。

  7. TurnServerAllocation 将该 IP 加入 perms_ 列表。

  8. 客户端发送 Send Indication 包含数据和目标 IP。

  9. TurnServerAllocation 检查权限,通过外部 Socket 将数据发给对端。

  10. 对端回复数据到中继端口。

  11. TurnServerAllocation 的 OnExternalPacket 被触发,检查权限/通道,将数据封装后发回给客户端。

相关推荐
换个昵称都难4 小时前
webrtc RTC_P2P源码解析
asp.net·webrtc·p2p
换个昵称都难4 小时前
webrtc StunServer源码介绍
webrtc
数据知道1 天前
指纹浏览器:DNS 泄漏防范与 WebRTC 本地 IP 屏蔽的底层实现
爬虫·网络协议·tcp/ip·安全·webrtc·数据采集·指纹浏览器
换个昵称都难2 天前
webrtc源码解析概要介绍
webrtc
换个昵称都难2 天前
WebRTC 完整调用流程(前端纯 JS 实现,最简可运行)
webrtc
换个昵称都难3 天前
webrtc 拥塞控制GCC 和PCC
webrtc
Cxiaomu3 天前
React接入WebRTC实时视频实践
react.js·音视频·webrtc
AndyHuang19763 天前
WebRTC 强制 Relay 模式下 TCP 重连失败深度排查与优化实战
webrtc
换个昵称都难3 天前
webrtc pacing 平滑发包模块
webrtc