WebRTC的ICE之TURN協議的交互流程和中繼轉發Relay媒體數據的turnserver的測試
WebRTC的ICE之TURN協議的交互流程中繼轉發Relay媒體數據的turnserver的測試
- WebRTC的ICE之TURN協議的交互流程和中繼轉發Relay媒體數據的turnserver的測試
- 前言
- 一、TURN協議
- 1、連接Turn Server 流程
- ① 抓包交換流程圖
- ② TURN協議格式解析
- 協議基礎與消息結構
- 關鍵方法類型
- 核心屬性類型
- 錯誤碼與狀態?
- 數據傳輸機制?
- ③ 發送Allocate Request UDP 信息包信息
- 再次發送 Allocate Request UDP 包 帶上 協議、用戶名和sha-1(user:realm:paasword)簽名
- ④ 發送CreatePermission Request
- 發送數據Send Indication
- 接收 Data Indiaction
- ⑤ Channel-Bind Request
- 發送數據 ChannelData Message
- 2、 TURN測試Demo
- 二 、 iceCandidate收集的時機有兩種
- 1、創建PeerConnection對象傳入的參數ice_candidate_pool_size大于0會創建連接池的個數, 如果沒有不會創建連接池ICE的速度就會有點慢
- 2、 設置sdp時獲取 iceCandidate
- 總結
前言
TURN協議:文檔RFC5766
文檔中TURN協議工作原理圖
Peer AServer-Reflexive +---------+Transport Address | |192.0.2.150:32102 | || /| |TURN | / ^| Peer A |Client's Server | / || |Host Transport Transport | // || |Address Address | // |+---------+10.1.1.2:49721 192.0.2.15:3478 |+-+ // Peer A| | ||N| / Host Transport| +-+ | ||A|/ Address| | | | v|T| 192.168.100.2:49582| | | | /+-++---------+| | | |+---------+ / +---------+| || |N| || | // | || TURN |v | | v| TURN |/ | || Client |----|A|----------| Server |------------------| Peer B || | | |^ | |^ ^| || | |T|| | || || |+---------+ | || +---------+| |+---------+| || | || || | |+-+| | || | || | |Client's | Peer BServer-Reflexive Relayed TransportTransport Address Transport Address Address192.0.2.1:7000 192.0.2.15:50000 192.0.2.210:49191
WebRTC中走中繼Relay 調用流程圖
一、TURN協議
TURN TURN Peer Peerclient server A B|-- Allocate request --------------->| | || | | ||<--------------- Allocate failure --| | || (401 Unauthorized) | | || | | ||-- Allocate request --------------->| | || | | ||<---------- Allocate success resp --| | || (192.0.2.15:50000) | | |// // // //| | | ||-- Refresh request ---------------->| | || | | ||<----------- Refresh success resp --| | || | | |
TURN TURN Peer Peerclient server A B| | | ||-- CreatePermission req (Peer A) -->| | ||<-- CreatePermission success resp --| | || | | ||--- Send ind (Peer A)-------------->| | || |=== data ===>| || | | || |<== data ====| ||<-------------- Data ind (Peer A) --| | || | | || | | ||--- Send ind (Peer B)-------------->| | || | dropped | || | | || |<== data ==================|| dropped | | || | | |
--------------------------------------------------------------------------------------
TURN TURN Peer Peerclient server A B| | | ||-- ChannelBind req ---------------->| | || (Peer A to 0x4001) | | || | | ||<---------- ChannelBind succ resp --| | || | | ||-- [0x4001] data ------------------>| | || |=== data ===>| || | | || |<== data ====| ||<------------------ [0x4001] data --| | || | | ||--- Send ind (Peer A)-------------->| | || |=== data ===>| || | | || |<== data ====| ||<------------------ [0x4001] data --| | || | | |Figure 4
1、連接Turn Server 流程
和國標gb28181的協議差不多 都是先發送一包AllocateRequest到服務返回一個錯誤碼然后在發送一個AllocateRequest請求帶上 用戶名和密碼 然后Trunserver返回映射turn上ip地址和port端口
① 抓包交換流程圖
② TURN協議格式解析
協議基礎與消息結構
TURN協議基于STUN協議擴展,采用STUN消息格式(頭部+屬性列表),同時新增了專用于中繼功能的方法和屬性?
- 消息頭(Header):
-
Class?:固定為0x00(請求)或0x10(指示消息)?
-
Method?:定義操作類型(如Allocate、ChannelBind等)?
-
Length?:消息總長度(不含頭部)?
? -
Transaction ID?:16字節唯一標識符,用于匹配請求與響應?
?2. 屬性列表(Attribute)?:
-
每個屬性由Type(2字節)、Length(2字節)和Value(可變長)組成?
-
例如:XOR-RELAYED-ADDRESS表示中繼地址,CHANNEL-NUMBER標識信道號?
關鍵方法類型
TURN協議新增以下STUN方法
- Allocate?:客戶端向服務器申請中繼地址,響應中包含XOR-RELAYED-ADDRESS屬性?
- Refresh?:延長中繼地址的生命周期(通過LIFETIME屬性設置超時時間)?
- ChannelBind?:綁定信道號(CHANNEL-NUMBER)與特定Peer地址,啟用ChannelData傳輸?
- Send/Data Indication?:用于中繼數據傳輸(Send為客戶端→服務器,Data為服務器→客戶端
核心屬性類型
新增屬性包括:
- XOR-RELAYED-ADDRESS?:服務器分配的中繼地址(IP+端口),用于標識客戶端的中繼端點?
- XOR-MAPPRE-ADDRES: 本機映射外網地址(IP+port)
- CHANNEL-NUMBER?:2字節信道號(范圍0x4000-0x7FFF),用于ChannelData報文的高效傳輸?
- LIFETIME?:中繼地址有效期(默認10分鐘),客戶端需定期刷新?
- DONT-FRAGMENT?:標志位,指示客戶端支持IP分片重組?
錯誤碼與狀態?
TURN協議擴展了STUN錯誤碼:
- 401(未認證)?:請求未攜帶有效憑證,需重新發送認證信息?78?
- 437(Allocation Mismatch)?:客戶端嘗試使用未分配的信道或地址?
- 508(Insufficient Capacity)?:服務器資源不足,無法分配中繼地址?
數據傳輸機制?
1?. Send/Data Indication?:
- 使用STUN消息格式,Send攜帶應用數據發送至服務器,Data由服務器轉發至Peer?
- 頭部包含XOR-PEER-ADDRESS屬性,標識目標Peer地址?67。
?
- ChannelData消息?:
- 非STUN格式,頭部僅4字節(信道號+數據長度),減少傳輸開銷?
- 需通過ChannelBind預先綁定信道號與Peer地址?
③ 發送Allocate Request UDP 信息包信息
第一次返回 401 Unauthorized 信息 帶有NONE(新生成的一次性隨機數),REALM(標識服務器或服務的域)
再次發送 Allocate Request UDP 包 帶上 協議、用戶名和sha-1(user:realm:paasword)簽名
TURN服務返回 本機映射外網地址和TURN服務分配中繼地址、有效時間
④ 發送CreatePermission Request
返回數據
發送數據Send Indication
!【】
接收 Data Indiaction
⑤ Channel-Bind Request
外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fgithub.com%2Fchensongpoixs%2Fcturn_relay_demo&pos_id=img-B8Tiv02W-1743192961278)
外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fchensongpoixs.github.io%2F&pos_id=img-om16XyoI-1743192922198)
發送數據 ChannelData Message
2、 TURN測試Demo
TURN測試demo
二 、 iceCandidate收集的時機有兩種
1、創建PeerConnection對象傳入的參數ice_candidate_pool_size大于0會創建連接池的個數, 如果沒有不會創建連接池ICE的速度就會有點慢
設置ice配置
bool Conductor::CreatePeerConnection(bool dtls) {RTC_DCHECK(peer_connection_factory_);RTC_DCHECK(!peer_connection_);RTC_LOG(LS_INFO) << __FUNCTION__;webrtc::PeerConnectionInterface::RTCConfiguration config;config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan; //這個 config.enable_dtls_srtp = dtls; //是否加密webrtc::PeerConnectionInterface::IceServer server;server.uri = GetPeerConnectionString();config.ice_candidate_pool_size = 0; // 大于0 就在sdp之前進行stun和turn連接創建, 反之啥事情都不做server.username = user_name_;server.password = pass_word_;std::vector<std::string> turnservers;turnservers.push_back(turn_url_);server.urls = turnservers;// 設置走 turn server 進行轉發媒體數據 哈config.type = webrtc::PeerConnectionInterface::kRelay;config.servers.push_back(server);peer_connection_ = peer_connection_factory_->CreatePeerConnection(config, nullptr, nullptr, this);return peer_connection_ != nullptr;
}
在CreatePeerConnection方法中
- 異步創建BasicPortAllocator對象中如果ice_candidate_pool_size大于在SetConfiguration方法中就會進行連接turn和stun服務
- 創建負責ICE管理類JsepTransportController
bool PortAllocator::SetConfiguration(const ServerAddresses& stun_servers,const std::vector<RelayServerConfig>& turn_servers,int candidate_pool_size,bool prune_turn_ports,webrtc::TurnCustomizer* turn_customizer,const absl::optional<int>& stun_candidate_keepalive_interval)
{CheckRunOnValidThreadIfInitialized();// A positive candidate pool size would lead to the creation of a pooled// allocator session and starting getting ports, which we should only do on// the network thread.RTC_DCHECK(candidate_pool_size == 0 || thread_checker_.IsCurrent());bool ice_servers_changed = (stun_servers != stun_servers_ || turn_servers != turn_servers_);stun_servers_ = stun_servers;turn_servers_ = turn_servers;prune_turn_ports_ = prune_turn_ports;if (candidate_pool_frozen_) {if (candidate_pool_size != candidate_pool_size_) {RTC_LOG(LS_ERROR) << "Trying to change candidate pool size after pool was frozen.";return false;}return true;}if (candidate_pool_size < 0) {RTC_LOG(LS_ERROR) << "Can't set negative pool size.";return false;}candidate_pool_size_ = candidate_pool_size;// If ICE servers changed, throw away any existing pooled sessions and create// new ones.///// 20250328 ice[stun、turn]信息改變 就清除當前端口連接池會話if (ice_servers_changed) {pooled_sessions_.clear();}turn_customizer_ = turn_customizer;// If |candidate_pool_size_| is less than the number of pooled sessions, get// rid of the extras.while (candidate_pool_size_ < static_cast<int>(pooled_sessions_.size())) {pooled_sessions_.back().reset(nullptr);pooled_sessions_.pop_back();}///// |stun_candidate_keepalive_interval_| will be used in STUN port allocation// in future sessions. We also update the ready ports in the pooled sessions.// Ports in sessions that are taken and owned by P2PTransportChannel will be// updated there via IceConfig.// ICE的心跳包 的stun_candidate_keepalive_interval_ = stun_candidate_keepalive_interval;for (const auto& session : pooled_sessions_) {session->SetStunKeepaliveIntervalForReadyPorts(stun_candidate_keepalive_interval_);}// If |candidate_pool_size_| is greater than the number of pooled sessions,// create new sessions.while (static_cast<int>(pooled_sessions_.size()) < candidate_pool_size_) {IceParameters iceCredentials = IceCredentialsIterator::CreateRandomIceCredentials();PortAllocatorSession* pooled_session = CreateSessionInternal("", 0, iceCredentials.ufrag, iceCredentials.pwd);pooled_session->set_pooled(true);//20250327 觸發MSG_CONFIG_START信號 探測stun和turn 服務連通性 pooled_session->StartGettingPorts();pooled_sessions_.push_back(std::unique_ptr<PortAllocatorSession>(pooled_session));}return true;
}
2、 設置sdp時獲取 iceCandidate
整體調用流程
調用流程圖
bool TurnPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,const char* data,size_t size,const rtc::SocketAddress& remote_addr,int64_t packet_time_us) {if (socket != socket_) {// The packet was received on a shared socket after we've allocated a new// socket for this TURN port.return false;}// This is to guard against a STUN response from previous server after// alternative server redirection. TODO(guoweis): add a unit test for this// race condition.if (remote_addr != server_address_.address) {RTC_LOG(LS_WARNING) << ToString()<< ": Discarding TURN message from unknown address: "<< remote_addr.ToString() << " server_address_: "<< server_address_.address.ToString();return false;}// The message must be at least the size of a channel header.if (size < TURN_CHANNEL_HEADER_SIZE) {RTC_LOG(LS_WARNING) << ToString()<< ": Received TURN message that was too short";return false;}if (state_ == STATE_DISCONNECTED) {RTC_LOG(LS_WARNING)<< ToString()<< ": Received TURN message while the TURN port is disconnected";return false;}// Check the message type, to see if is a Channel Data message.// The message will either be channel data, a TURN data indication, or// a response to a previous request.uint16_t msg_type = rtc::GetBE16(data);if (IsTurnChannelData(msg_type)) {HandleChannelData(msg_type, data, size, packet_time_us);return true;}if (msg_type == TURN_DATA_INDICATION) {HandleDataIndication(data, size, packet_time_us);return true;}if (SharedSocket() && (msg_type == STUN_BINDING_RESPONSE ||msg_type == STUN_BINDING_ERROR_RESPONSE)) {RTC_LOG(LS_VERBOSE)<< ToString()<< ": Ignoring STUN binding response message on shared socket.";return false;}// This must be a response for one of our requests.// Check success responses, but not errors, for MESSAGE-INTEGRITY.
#if TURN_LOGstd::cout << "[turn][hash() = " << hash() << "]" << std::endl;RTC_LOG(LS_INFO) << "[turn][hash() = " << hash() << "]";
#endif // #if TURN_LOGif (IsStunSuccessResponseType(msg_type) &&!StunMessage::ValidateMessageIntegrity(data, size, hash())) {RTC_LOG(LS_WARNING) << ToString()<< ": Received TURN message with invalid ""message integrity, msg_type: "<< msg_type;return true;}request_manager_.CheckResponse(data, size);return true;
}
總結
WebRTC源碼分析地址:https://github.com/chensongpoixs/cwebrtc