MediaSoup建连过程

本篇梳理下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();
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值