本篇梳理下MediaSoup与WebRtc交互时建连流程,主要包括SDP协商(参考之前《MediaSoup的SDP协商》)、ICE处理、DTLS处理流程。
1.SDP协商
参考《MediaSoup的SDP协商》。
2.ICE处理
客户端收到AnswerSdp之后,将其中的ice-ufrag和ice-pwd取出来,发送Stun包的Binding Request请求,服务端接收之后:
2.1 封装TransportTuple对象
MediaSoup使用TransportTuple来管理一组UdpSocket,这里记录下本地UdpSocket和远端UdpSocket的映射。
inline void WebRtcTransport::OnUdpSocketPacketReceived(
RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr)
{
MS_TRACE();
// 将连接封装成一个tuple对象
RTC::TransportTuple tuple(socket, remoteAddr);
OnPacketReceived(&tuple, data, len);
}
// tuple构造函数:
TransportTuple(RTC::UdpSocket* udpSocket, const struct sockaddr* udpRemoteAddr)
: udpSocket(udpSocket), udpRemoteAddr((struct sockaddr*)udpRemoteAddr),
protocol(Protocol::UDP)
{
}
2.2 封装Stun包格式
inline void WebRtcTransport::OnPacketReceived(
RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
MS_TRACE();
// Increase receive transmission.
RTC::Transport::DataReceived(len);
// Check if it's STUN.
if (RTC::StunPacket::IsStun(data, len))
{
// 如果是STUN包
OnStunDataReceived(tuple, data, len);
}
// ...
else
{
MS_WARN_DEV("ignoring received packet of unknown type");
}
}
// 将数据封装成STUN包格式
inline void WebRtcTransport::OnStunDataReceived(
RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
MS_TRACE();
// 解析成stun包格式
RTC::StunPacket* packet = RTC::StunPacket::Parse(data, len);
if (!packet)
{
MS_WARN_DEV("ignoring wrong STUN packet received");
return;
}
// Pass it to the IceServer.
this->iceServer->ProcessStunPacket(packet, tuple);
delete packet;
}
2.3 校验ICE参数
通过ice-ufrag和ice-pwd参数来校验本次请求是否有效:
void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple)
{
// ...
switch (packet->GetClass())
{
case RTC::StunPacket::Class::REQUEST:
{
// ...
// 校验ice-ufrag和ice-pwd是否有效
switch (packet->CheckAuthentication(this->usernameFragment, this->password))
{
case RTC::StunPacket::Authentication::OK:
{
if (!this->oldPassword.empty())
{
MS_DEBUG_TAG(ice, "new ICE credentials applied");
this->oldUsernameFragment.clear();
this->oldPassword.clear();
}
break;
}
// ...
}
// ice角色,远端必须是ice controlling
if (packet->GetIceControlled())
{
// Reply 487 (Role Conflict).
RTC::StunPacket* response = packet->CreateErrorResponse(487);
response->Serialize(StunSerializeBuffer);
this->listener->OnIceServerSendStunPacket(this, response, tuple);
delete response;
return;
}
// 生成一个成功的回复.
RTC::StunPacket* response = packet->CreateSuccessResponse();
// Add XOR-MAPPED-ADDRESS.
response->SetXorMappedAddress(tuple->GetRemoteAddress());
// Authenticate the response.
if (this->oldPassword.empty())
response->Authenticate(this->password);
else
response->Authenticate(this->oldPassword);
// Send back.
response->Serialize(StunSerializeBuffer);
delete response;
// 处理这次请求
HandleTuple(tuple, packet->HasUseCandidate());
break;
}
// ...
}
}
2.4 处理Stun请求
void IceServer::HandleTuple(RTC::TransportTuple* tuple, bool hasUseCandidate)
{
MS_TRACE();
switch (this->state)
{
case IceState::NEW:
{
// ...
MS_DEBUG_TAG(ice, "transition from state 'new' to 'completed'");
// 将这个tuple保存下来,也就是说,客户端只要带有ice-ufrag和ice-pwd
// 校验通过后,都认为他是一个有效的tuple
auto* storedTuple = AddTuple(tuple);
// 将该tuple设置为当前使用的tuple
SetSelectedTuple(storedTuple);
// 设置Ice状态为completed
this->state = IceState::COMPLETED;
// dtls相关处理
this->listener->OnIceServerCompleted(this);
break;
}
// ...
}
}
}
2.4.1 添加/选定Tuple信息
原则上说,只要通过ice-ufrag和ice-pwd校验,都认为他是有效的,都会封装成TransportTuple,会保存下所有的有效的TransportTuple。
inline RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple)
{
MS_TRACE();
// Add the new tuple at the beginning of the list.
this->tuples.push_front(*tuple);
auto* storedTuple = std::addressof(*this->tuples.begin());
// If it is UDP then we must store the remote address (until now it is
// just a pointer that will be freed soon).
if (storedTuple->GetProtocol() == TransportTuple::Protocol::UDP)
storedTuple->StoreUdpRemoteAddress();
// Return the address of the inserted tuple.
return storedTuple;
}
inline void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple)
{
MS_TRACE();
// If already the selected tuple do nothing.
if (storedTuple == this->selectedTuple)
return;
this->selectedTuple = storedTuple;
// 通知应用层现在使用的tuple信息,包括本地ip/port,远端ip/port,使用的协议udp/tcp
this->listener->OnIceServerSelectedTuple(this, this->selectedTuple);
}
至此,ICE的校验流程完成了。
3. DTLS处理
DTLS 适用于加密 UDP 通信过程,SSL/TLS 适用于加密 TCP 通信过程。在WebRTC
中需要注意一点,DTLS只用在SRTP密钥交换过程中,密钥交换完成后收发SRTP过程中就不需要DTLS了。
注意:DTLS处理比较麻烦,这里分了三块来写,《3.1 初始化本地DTLS相关信息》是在MediaSoup启动时就去做的;《3.2 SDP协商》是SDP协商阶段来做的,参考《Mediasoup的SDP协商》;《3.3 DTLS握手处理》是在ICE校验完成之后做的。
3.1 初始化本地DTLS相关信息
首先要初始化本地的DTLS信息,包括加载或创建证书和私钥、创建的证书和密钥与SSL关联、生成Fingerprint信息。
void DtlsTransport::ClassInit()
{
MS_TRACE();
// Generate a X509 certificate and private key (unless PEM files are provided).
if (
Settings::configuration.dtlsCertificateFile.empty() ||
Settings::configuration.dtlsPrivateKeyFile.empty())
{
// 创建证书、私钥
GenerateCertificateAndPrivateKey();
}
else
{
// 加载证书、私钥
ReadCertificateAndPrivateKeyFromFiles();
}
// 创建的证书和密钥与SSL关联
CreateSslCtx();
// 生成Fingerprint信息
GenerateFingerprints();
}