马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
mediasoup 提供了多种 transport,包罗 WebRtcTransport、PipeTransport、DirectTransport、PlainTransport 等,用来实现不同目的和场景的媒体通信。WebRtcTransport 是 mediasoup 实现与 WebRTC 客户端举行媒体通信的对象,是 mediasoup 最重要也是最复杂的 transport,明白了 WebRtcTransport 的筹划与实现,再去分析其他类型 transport 会简单很多。
1. 静态结构
WebRtcTransport 可以创建独立通信端口,也可以共享 WebRtcServer 上的通信端口,从安全和运维复杂度来讲,大部分场景都应该选择第二种方式,本文重要分析第二种实现方式。
1.1. WebRtcTransport
1.1.1. 接口继续
1)Transport
这是所有 transport 的基类,代表与具体协议无关的通信端口,它封装了通信端口到场上层数据互换的公共本事,比如如何与 router、producer、consumer 的关联与交互等。
2)UdpSocket: istener
WebRtcTransport 建立独立通信端口时,吸收 UDP 数据。
- class Listener
- {
- public:
- virtual void OnUdpSocketPacketReceived(
- RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr) = 0;
- };
复制代码 3)TcpServer: istener
WebRtcTransport 建立独立通信端口时,处置惩罚 TCP 连接关闭。
- class Listener
- {
- public:
- virtual void OnRtcTcpConnectionClosed(
- RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) = 0;
- };
复制代码 4)TcpConnection: istener
WebRtcTransport 建立独立通信端口时,吸收 TCP 数据。
- class Listener
- {
- public:
- virtual void OnTcpConnectionPacketReceived(
- RTC::TcpConnection* connection, const uint8_t* data, size_t len) = 0;
- };
复制代码 5)IceServer: istener
ICE 交互相干回调,具体见注释。
- class Listener
- {
- public:
- // 通知发送 stun 报文
- virtual void OnIceServerSendStunPacket(
- const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) = 0;
- // 通知添加/删除 ufrag,主要用于 WebRtcServer stun 报文路由
- virtual void OnIceServerLocalUsernameFragmentAdded(
- const RTC::IceServer* iceServer, const std::string& usernameFragment) = 0;
- virtual void OnIceServerLocalUsernameFragmentRemoved(
- const RTC::IceServer* iceServer, const std::string& usernameFragment) = 0;
- // 通知添加/删除 tuple,用于 WebRtcServer 非 stun 报文路由
- virtual void OnIceServerTupleAdded(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;
- virtual void OnIceServerTupleRemoved(
- const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;
- virtual void OnIceServerSelectedTuple(
- const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;
- // WebRTC 客户端与服务器通信端口连接状态变化通知
- virtual void OnIceServerConnected(const RTC::IceServer* iceServer) = 0;
- virtual void OnIceServerCompleted(const RTC::IceServer* iceServer) = 0;
- virtual void OnIceServerDisconnected(const RTC::IceServer* iceServer) = 0;
- };
复制代码 6)DtlsTransport: istener
DTLS 相干回调,详见注释。
- class Listener
- {
- public:
- // 接收方向协商成功
- virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;
- // 双向协商成功
- virtual void OnDtlsTransportConnected(
- const RTC::DtlsTransport* dtlsTransport,
- RTC::SrtpSession::CryptoSuite srtpCryptoSuite,
- uint8_t* srtpLocalKey,
- size_t srtpLocalKeyLen,
- uint8_t* srtpRemoteKey,
- size_t srtpRemoteKeyLen,
- std::string& remoteCert) = 0;
- // 连接失败或异常中断
- virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;
- // 对方关闭 DTLS 连接(close_notify alert)
- virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;
- // 向对端发送 DTLS 数据(DTLS协议交互)
- virtual void OnDtlsTransportSendData(
- const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
- // 收到 DTLS 应用层数据
- virtual void OnDtlsTransportApplicationDataReceived(
- const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;
- };
复制代码 1.1.2. 重要属性
1)webRtcTransportListener
重要用于 WebRtcTransport 通知 WebRtcServer 相干信息用来建立数据路由。
- class WebRtcTransportListener
- {
- public:
- virtual void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) = 0;
- virtual void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) = 0;
- // ICE 协议交互阶段需要使用 ufrag 来建立路由
- virtual void OnWebRtcTransportLocalIceUsernameFragmentAdded(
- RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0;
- virtual void OnWebRtcTransportLocalIceUsernameFragmentRemoved(
- RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0;
- // ICE 协议交互完成后需要使用 TransportTuple 来建立路由
- virtual void OnWebRtcTransportTransportTupleAdded(
- RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0;
- virtual void OnWebRtcTransportTransportTupleRemoved(
- RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0;
- };
复制代码 2)iceServer
处置惩罚 ICE 协议交互,确定用于通信的 TransportTuple。
3)udpSockets
WebRtcTransport 建立独立通信端口的 UDP socket。
4)tcpServers
WebRtcTransport 建立独立通信端口的 TCP 监听器。
5)dtlsTransport
处置惩罚 DTLS 协商,协商得到 SRTP 所需的加解密参数。
6)srtpRecvSession
用于吸收方向的 SRTP 解密。
7)srtpSendSession
用于发送方向的 SRTP 加密。
1.2. IceServer
1.2.1. 重要属性
1)usernameFragment 和 password
本端生成的随机值,对应 SDP 中的 ice-ufrag 和 ice-pwd
2)oldUsernameFragment 和 oldPassword
用来帮忙处置惩罚 ICE 重协商。
2)consentTimeoutMs
客户端需要定时发送 Bind 哀求举行保活,这是相干超时设置。
4)state
ICE 协商状态,具体见下面的状态机分析。
- enum class IceState
- {
- NEW = 1,
- CONNECTED,
- COMPLETED,
- DISCONNECTED,
- };
复制代码 5)remoteNomination
正常环境是利用 USE-CANDIDATE 属性来选择用来通信的 candidate,当没有 USE-CANDIDATE 属性时,但携带了 NOMINATION 属性,则可以利用 nomination 来选择通信 candidate,值越大地址优先级越高。
6)tuples
保存吸收到客户端所有 BIND 哀求的地址。
7)selectedTuple
最终选择利用的通信地址对。
1.2.2. 重要方法
1)ProcessStunPacket
WebRtcTransport 调用,用来处置惩罚 BIND 哀求。
2)RestartIce
当客户端发起 ICE 重协商时调用此接口,此时会重新生成 ufrag 和 pwd。
1.3. DtlsTransport
DtlsTransport 用来处置惩罚 DTLS 协议交互,完成身份验证和密钥协商。一旦 DTLS 完成握手并协商好密钥,后续RTP报文就不再通过 DTLS 处置惩罚,而是通过 SRTP 举行加密息争密。
1.3.1. 重要属性
1)listener
用来通知 DTLS 连接状态,DTLS 协议报文需要经过 WebRtcTransport 发送,别的, SCTP报文会被封装在DTLS报文中举行传输,通过 OnDtlsTransportApplicationDataReceived 回调收到的 SCTP 数据。
2)ssl
处于 SSL 协议交互的 OpenSSL 对象。
3)state
DTLS 连接状态,具体见 DTLS 状态机分析。
- enum class DtlsState
- {
- NEW = 1,
- CONNECTING,
- CONNECTED,
- FAILED,
- CLOSED
- };
复制代码 4)localRole
DTLS 脚色重要用于确定DTLS握手过程中的通信方向和权限,通常是 CLIENT 发起握手。
- enum class Role
- {
- AUTO = 1,
- CLIENT,
- SERVER
- };
复制代码 mediasoup 根据客户端 DTLS 脚色来设置本端 DTLS 脚色(调用 connectWebRtcTransport 传递),并返回协商结果给客户端。
- // Set local DTLS role.
- switch (dtlsRemoteRole)
- {
- case RTC::DtlsTransport::Role::CLIENT:
- {
- this->dtlsRole = RTC::DtlsTransport::Role::SERVER;
- break;
- }
- // If the peer has role "auto" we become "client" since we are ICE controlled.
- case RTC::DtlsTransport::Role::SERVER:
- case RTC::DtlsTransport::Role::AUTO:
- {
- this->dtlsRole = RTC::DtlsTransport::Role::CLIENT;
- break;
- }
- }
复制代码 5)remoteFingerprint
保存客户端的证书指纹,用来验证 DTLS 协商时对端证书的合法性。
6)remoteCert
保存客户端的证书。
1.3.2. 重要方法
1)ProcessDtlsData
WebRtcTransport 调用此接口传入 DTLS 报文。
2)SetRemoteFingerprint
在建立 DTLS 连接之前,服务器需要调用此接口设置客户端的证书指纹,否则无法对客户端证书举行验证。
3)SendDtlsData
OpenSSL 回调需要发送协商报文,内部会调用回调 WebRtcTransport 举行发送。
4)SendApplicationData
发送 SCTP 数据。
2. 数据流
WebRtcTransport 在举行媒体通信前要履历两个阶段,第一个阶段是 ICE 协议交互,用来确定举行通信的地址对,第二个阶段是 DTLS 协议交互,用来实现身份认证和密钥协商。这两个阶段完成后才开始举行 RTP/RTCP 传输阶段,并利用 DTLS 阶段协商的密钥对淑军举行加解密。
2.1. STUN
WebRtcServer 收到 UDP 数据时,会剖析报文特征,识别报文类型,然后调用 ProcessStunPacketFromWebRtcServer 处置惩罚 STUN 报文,WebRtcTransport 将 STUN 报文丢给 IceServer 处置惩罚。IceServer 处置惩罚完后,假如需要发送响应的则会回调 WebRtcTransport,WebRtcTransport 调用 TransportTuple 将报文发送出去。
但这里有个问题,WebRtcServer 如何知道 STUN 报文属于哪个 WebRtcTransport?方法是,WebRtcTransport 在创建 IceServer时,会将 ice-ufrag 与 WebRtcTransport 关联起来。WebRtcServer 通过剖析 STUN 报文的 ufrag 字段找到所属 WebRtcTransport。
同时,WebRtcTransport 还会将 ICE 交互过程中所有地址对设置到 WebRtcServer,这样后续从这些地址发送过来的报文都送到关联的 WebRtcTransport 处置惩罚,不用再依靠 ufrag 字段。固然,非 STUN 报文也没有这个字段可用。
2.2. DTLS
DTLS 的数据流与 STUN 数据流类似,只是在 WebRtcTransport 会对非 STUN 数据举行剖析,假如是 DTLS 协议报文会调用 DtlsTransport: rocessDtlsData 举行处置惩罚。假如需要发送 DTLS 协议报文,也是回调 WebRtcTransport 发送。
2.3. RTP
RTP 数据流相对复杂一些,涉及到转发层的路由,如下图所示。
1)WebRtcTransport 将收到的 RTP 报文交给 Transport 处置惩罚。
2)Transport 通过报文的 SSRC 找到对应的 Producer,然后将报文交给 Producer 处置惩罚。
【注】在 Worker 上创建 Producer 时,需要传入 RtpParameters,里面包含了 SSRC、MID、RID 等信息。Worker 会将这些信息与 Producer 关联起来,当收到报文时可以基于这些信息找到对应的 Producer。
3)Producer 需要做拥塞控制、NACK、关键帧哀求等相干处置惩罚。处置惩罚完后,会回调 Transport 举行后续的报文转发。
4)Transport 直接将报文转发给 Router 处置惩罚。
【注】Transport 是在 Router 上创建的,Transport 只能属于某个 Router。
5)Router 将报文转发给所有连接到 Producer 的 Consumer。
6)Consumer 也需要做拥塞控制,NACK、关键帧哀求等相干处置惩罚。处置惩罚完后,会回调 Consumer 所在 Transport,将报文发送出去。
3. 增补分析
3.1. 证书指纹
证书指纹(fingerprint)用来验证对等端的合法性,有效防止中间人攻击。证书中不携带证书指纹,需要通过其他方式来传递和互换。WebRTC 在 SDP 中携带证书指纹,如下所示。fingergpinrt 分为两段,第一段为择要算法类型,第二段为利用此择要算法盘算的证书择要值。
- a=fingerprint:sha-512 28:8D:69:62:88:27:68:0B:41:FB:BE:28:DE:63:F0:2D:7C:AA:38:72:57:58:37:D4:BD:B9:BE:01:9D:A1:AF:86:1D:BB:9F:36:76:04:A8:0D:24:80:5C:08:D7:70:0D:BA:54:06:CC:48:27:52:DE:00:CD:72:B3:1A:E6:15:F1:7D
复制代码 mediasoup 的证书指纹通过 connectWebRtcTransport 流程传递到 Worker,如下图所示:
证书指纹的盘算方式可以简单的形貌为:基于 X509 规范对证书内容利用指定的哈希算法盘算择要。
- ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
复制代码 在 DTLS 握手过程中,Worker 会验证对等端的证书与设置的指纹是否匹配, 假如匹配,则验证通过,握手继续举行;假如不匹配,则握手失败,连接终止。
3.2. 密钥协商
mediasoup 预置支持的加密套件如下所示:
- std::vector<DtlsTransport::SrtpCryptoSuiteMapEntry> DtlsTransport::srtpCryptoSuites =
- {
- { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" },
- { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" },
- { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" },
- { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" }
- };
复制代码 通过下面代码设置将支持的 SRTP 的加密套件设置到 SSL。
- // 设置SRTP加密套件
- ret = SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpCryptoSuites.c_str());
复制代码 最终协商结果是两套密钥(盐),客户端和服务器利用独立的密钥举行加密。
- // Create the SRTP local master key.
- std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
- std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
- // Create the SRTP remote master key.
- std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
- std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
复制代码 3.3. 重启 ICE
假如网络条件发生变化(比方,网络断开或切换),则大概需要重新启动ICE进程,以便重新发现和建立有效的网络连接。如下图所示,假如在 DEMO 上点击所示图标,会触发 ICE 重启。
客户端先调用服务器接口,获取服务器更新后的 ICE 参数,利用新的 ICE 参数更新 remote SDP,再举行 PeerConnection 的 SDP 重协商。
- async restartIce(iceParameters: IceParameters): Promise<void> {
- this.assertNotClosed();
- // 更新 ICE 参数(ufrag/pwd)
- this._remoteSdp!.updateIceParameters(iceParameters);
- if (!this._transportReady) {
- return;
- }
- // SDP 重协商
- if (this._direction === 'send') {
- const offer = await this._pc.createOffer({ iceRestart: true });
- await this._pc.setLocalDescription(offer);
- const answer = { type: 'answer', sdp: this._remoteSdp!.getSdp() };
- await this._pc.setRemoteDescription(answer);
- } else {
- const offer = { type: 'offer', sdp: this._remoteSdp!.getSdp() };
- await this._pc.setRemoteDescription(offer);
- const answer = await this._pc.createAnswer();
- await this._pc.setLocalDescription(answer);
- }
- }
复制代码 3.4. ICE 状态机
USE_CANDIDATE 属性用于指示特定的候选对(candidate pair)被选为用于传输。mediasoup 收到 Binding 哀求假如携带 USE_CANDIDATE 属性则进入 COMPLETED 状态,否则进入 CONNECTED 状态。客户端要定时向服务器发送 binding 哀求来保活,假如超时,则进入 DISCONNECTED 状态。
3.5. DTLS 状态机
DTLS 状态比力简单,在 CONNECTING 状态,假如证书验证失败、加密套件协商失败大概协商超时,都会进入 FAILED 状态。在 CONNECTED 状态假如收到关闭哀求,则进入到 CLOSED 状态。DTLS 连接成功后,好像没有保活处置惩罚,看起来像是依靠于 ICE 的保活。
4. 总结
本文重点分析了 WebRtcTransport 的静态结构及重要数据流,对于明白 mediasoup 媒体转发框架非常重要。建立 WebRtcTransport 需要履历 ICE 协商和 DTLS 协商两个阶段,本文只对其中几个比力重要的逻辑举行了分析,不涉及 ICE 协商和 DTLS 协商的协议细节,如有需要请参考其他文档。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |