这段时间协助其它同事开发音乐机器人,遇到如题的BUG,花了好几天才做了个work around方案,现总结如下。
python有个著名的aiortc,aioice等,支撑了python语言开发webrtc方面的应用,我们在开发音乐机器人的时候使用到了该库:https://github.com/aiortc/aiortc,在例子里有个janus的videoroom的demo,可是我们在运行这个demo的时候确遇到了一个问题就是怎么都看不到视频听不到声音,media link quality却是100;打印demo的log出来,也不见任何异常,查了janus log出现以下信息:
[8220206318395062] New selected pair for component 1 in stream 1: 1 <-> remote2
[8220206318395062] Component is connected, starting DTLS handshake...
[8220206318395062] Component state changed for component 1 in stream 1: 4 (ready)
[8220206318395062] DTLSv1_get_timeout: 977
[8220206318395062] DTLSv1_get_timeout: 925
[4290012674097374] >> >> ... and sent 735 of those bytes on the socket
[4290012674097374] ... and read -1 of them from SSL...
[4290012674097374] Initialization not finished yet...
[WARN] [4290012674097374] Missing valid SRTP session (packet arrived too early?), skipping...
[4290012674097374] Looks like DTLS!
[4290012674097374] Written 1500 bytes on the read BIO...
[4290012674097374] ... and read -1 of them from SSL...
[4290012674097374] Initialization not finished yet...
[WARN] [4290012674097374] Missing valid SRTP session (packet arrived too early?), skipping...
[4290012674097374] DTLSv1_get_timeout: 962
[WARN] [4290012674097374] Missing valid SRTP session (packet arrived too early?), skipping...
[WARN] [4290012674097374] Missing valid SRTP session (packet arrived too early?), skipping...
[4290012674097374] DTLSv1_get_timeout: 910
......
[8220206318395062] DTLS timeout on component 1 of stream 1, retransmitting
即DTLS timeout,那么DTLS是个什么概念呢?即我们所有的音视频媒体帧都是通过UDP端口发送的,为了安全需要对这个UDP的链路也做了类似TCP的SSL握手,DTLS就是UDP + TSL,这个信息会在sdp协商的时候指定(m=video 9 UDP/TLS/RTP/SAVPF 97 99 101)。这样在ICE协商完成之后就会启动DTLS握手流程,最终我们的数据会以SCTP的格式传输。关于DTLS的协商有个系列的文章描述得非常详细,现链接如下:
HTTPS 温故知新(三) —— 直观感受 TLS 握手流程(上)
HTTPS 温故知新(三) —— 直观感受 TLS 握手流程(上)
再次抓包分析:
对比个正常得DTLS握手:
理论上是,参考RFC5764的定义(https://tools.ietf.org/html/rfc5764):
即服务器端未能收到ChangCipherSpec和Finished消息,这样类似的问题在aiortc上有好几个issue:
https://github.com/aiortc/aiortc/issues/365
https://github.com/aiortc/aiortc/issues/346
有人通过升级openssl到1.1.1的版本解决了这个问题,但是我们已经是1.1.1d版本了依然还存在这个问题,其实这个问题是DTLS中一个普遍的问题,Mediasoup也出现过这个问题:
https://blog.csdn.net/sinat_17736151/article/details/86439405
可是我按照他们的方法在服务端和客户端都设置了SSL_set_mtu和DTLS_set_link_mtu为1350和1472都没有成功:
其实这个问题的原因就是在openssl中使用BIO来替代UDP传输协议,这个BIO又分为两种,source/link BIO和filter BIO(见https://www.openssl.org/docs/man1.1.0/man3/bio.html),前者是不支持消息大小超过MTU的,如果我们不修改MTU大小,那么BIO出去的数据一旦超过MTU大小则会在网络层面丢失,而且对端opensssl的BIO也是处理不了的;如果我们使用filter BIO则可以接收、处理和发送超过MTU大小的消息,这样的特性在janus服务中已经实现,我们可以看下janus日志:
[5418194469749675] Looks like DTLS!
[5418194469749675] Written 1500 bytes on the read BIO...
[5418194469749675] ... and read -1 of them from SSL...
[5418194469749675] Initialization not finished yet...
服务器能够发送包含Finished的消息在内的1500个字节,但是因为aiortc没有使用filter BIO去处理数据,导致aiortc侧发送的finished的消息在网络上丢包了,于是janus没有收到finished消息,DTLS握手超时,循环启动握手流程。
总结一下,解决的办法有两个:
修改证书大小使消息包的大小不超过MTU大小(修改网卡MTU大小无用,因为网络中还有中间节点);
在aiortc层实现filter BIO
对于第二种方法目前难度较大,因aiortc依赖cryptography(python语言的)库封装c的openssl,但是尚未提供filter BIO的接口,也未实现SSL_set_mtu接口。
对于第一种生成了1024位的证书测试通过,貌似2048和4084位的证书都不行:
自签名证书的命令是:
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:1024 -keyout privateKey.key -out certificate.crt
我们也使用了go语言的webrtc框架,见pion(https://github.com/pion/dtls)的实现,这个dtls的实现完全采用go语言开发,未依赖c语言的openssl实现,所以未出现DTLS timeout这个不太友好的缺陷。最后向这类务实的开发者致敬,让我们在go语言领域非常轻松地开发webrtc的应用。
又翻了下janus_gateway的实现,关于DTLS timeout的处理见:
https://github.com/meetecho/janus-gateway/issues/252
https://github.com/meetecho/janus-gateway/pull/254/commits/58409e86b621641cf8a64dc0c0765466f49d9196
https://mta.openssl.org/pipermail/openssl-users/2015-June/001503.html