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 A
Server-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 B
Server-Reflexive Relayed Transport
Transport Address Transport Address Address
192.0.2.1:7000 192.0.2.15:50000 192.0.2.210:49191
WebRTC中走中继Relay 调用流程图
一、TURN协议
TURN TURN Peer Peer
client 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 Peer
client 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 Peer
client 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_LOG
std::cout << "[turn][hash() = " << hash() << "]" << std::endl;
RTC_LOG(LS_INFO) << "[turn][hash() = " << hash() << "]";
#endif // #if TURN_LOG
if (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