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