Browsed by
Category: 通信技术

通信,让生活更美好!

为什么放弃了webRTC?

为什么放弃了webRTC?

首先必须要说明的是:webRTC是非常好的技术,以至于我现在仍然在怀疑,放弃webRTC是不是个明智的决定,内心依然是忐忑不安。

然而现状就是将webRTC从MSS新版本中砍掉了。下面试图说明清楚做决定时的一些考虑。

从webRTC技术的发展脉络看,感觉ta更适合于公共网络的通信,尤其是越来越像专为google hangouts服务。由于是服务于公共网络,因此“加密”提高到一个非常重要的位置,几乎达到了神经质的地步。webRTC设计之初就没有考虑过传统的VoIP网络(尽管该技术来自于收购的GIPS团队,而该团队本身就是为VoIP网络开发出身的),从传输到业务,基本都特立独行。当然,这没什么不好,只是由于与传统VoIP网络切割开来,实在让人经常感到惋惜。

既然没有考虑传统VoIP网络,当然就更不可能考虑网络部署的多样性。全方位加密固然让自己显得安全,带来心理上的安慰,但也增加了网络部署的复杂性(VoIP本身已经够复杂了)。就一般的VoIP应用而言,“salt+MD5”以及SRTP已经足够保证通信的安全性和私密性。而最新的webRTC(不特别说明的话,这里我们总是指Chrome的webRTC实现)要求getUserMedia等操作必须是来自加密(HTTPS)的网站,websocket也必须进行加密。这也就是说,用户必须要为webRTC服务器(通常也是呼叫服务器或者IPPBX)部署单独的签名加密(TLS或者SSL)。对于提供公众服务的网站而言,部署TLS/SSL不是太大问题,而要求中小企业申请签名并部署在呼叫服务器中,这毫无疑问极大地增加了系统的复杂度,增加了部署难度,而且这样做的意义又有多大了?在企业通信网络里与其这样增加复杂度(网络复杂度和管理复杂度),还不如干脆直接建立VPN网络,方案很成熟,加密更全面,保护更周到,方式方法也简单得多。

这就是我们感觉webRTC只适合公众网络并且已经神经质的原因之一。何况,“加密”有时候更多的是个管理问题而不是技术问题。

webRTC另一个宏大的目标是提供各平台统一的用户体验。愿望是很美好的,但是是否能实现值得商榷。把所有的处理都放入浏览器,固然增加了可移植性,但最大的问题也就是牺牲了平台的独特性和高效性。比如在移动平台,webRTC的耗电量就明显偏高。而且在移动平台各种莫名其妙的设定,极大地损害了用户体验,比如播放ring-back tone,居然要求用户必须点击一个按钮才能继续(是我out了么?),这实在是太扯了。

而最被我们诟病的是兼容性。webRTC基本没有什么兼容性可言。按道理已经发布这么久了,多少应该在一些关键点上考虑一下兼容性,然而并没有。这对一个像google hangouts这样的网站来说,不是大问题,用户无非升级一下chrome好了。而对部署在许许多多用户voip网络中的PBX或者webRTC服务器而言,就是噩梦了。用户升级了Chrome,就必须同时升级服务器,如果不升级服务器,就必须降级Chrome。与之相比,microsoft对兼容性的考虑简直贴心贴肺。

这种对兼容性的唾弃有时候甚至直接表现在产品本身。比如当初的Gtalk,直接就放弃了,我们花费大量时间精力对接Gtalk,最后也是然并卵。对webRTC也会产生同样的顾虑。

webRTC当然有很多非常优秀的技术特点,比如源自GIPS的回音消除技术、超强的语音编解码技术等。思虑再三,决定还是不再跟随,砍了算了,以后再说吧。

实现一个简单的SMTP发送库

实现一个简单的SMTP发送库

最近在优化“语音邮箱”业务时,发现采用python-smtplib库虽然能满足需要,但是还是有很多不足,因此决定自己重新写一个库,当然仅仅实现发送email(包含附件)即可。实现一个完整的SMTP服务可能比较复杂,而实现一个单纯的、高性能的SMTP客户端发送库却比较容易。

与SIP、HTTP等协议类似,SMTP也采用文本方式定义消息格式,而附件等二进制内容,则采用base64转码成文本,这样就极为方便开发和调试。以下是SMTP最基础的几个RFC文档:

RFC5321: SMTP
RFC5322: Internet message format
RFC4954: SMTP Service Extension for Authentication
RFC3207: SMTP Service Extension for Secure SMTP over Transport Layer Security
RFC2035: Multipurpose Internet Mail Extensions

我们结合SMTP的流程,来简要说明SMTP协议中的各个关键点。

一、标准端口

SMTP基于TCP连接,SMTP服务器默认打开的端口是25、587或者465。通常是采用25端口,后期设计加密SMTP(即SMTPS)时采用了465端口。而IANA在新业务端口规划时占用了465端口,因此将587端口定义为TLS加密的SMTP端口。

目前各email服务商通常都会同时打开25和587端口,有些为了和以前老客户端程序兼容,也会打开465端口。

这里面其实可能存在一个误解,似乎25端口就是不加密的SMTP,而587、465就是加密的SMTP。实际上不是这样,现代SMTP服务器一般都已经支持STARTTLS(即RFC3207),因此在25端口也可以启动SSL/TLS加密,这取决于客户端是否具备SSL/TLS加密、解密能力,这点在后续流程中会详细描述。

二、基础流程

2.1 建立与SMTP服务器的连接

建立TCP连接,连接到服务器的25端口或者587端口。如果是25端口,则是建立普通TCP连接即可。如果是587端口,通常可以直接建立SSL/TLS连接。需要注意的是,有些SMTP服务器的587端口也是要求普通TCP连接,后续STARTTLS流程中再启动SSL/TLS加密。这种情况下,587端口的流程与25端口的流程一致。

我们的示例流程中,采用QQMail的SMTP服务器做参考,因此我们首先向“smtp.qq.com”服务器的25端口建立连接。连接成功后,服务器应返回220消息,告诉客户端连接成功:

220 smtp.qq.com Esmtp QQ Mail Server

2.2 初次握手

SMTP客户端发送EHLO命令给服务器,进行初次握手协商:

EHLO 192.168.1.101

“EHLO”实际是ESMTP(Extended SMTP)的指令,意思是要进行用户名和密码的鉴权。远古时代的SMTP不需要鉴权的情况下,可以直接发送HELO命令进行握手。目前的SMTP服务器基本都是支持ESMTP,因此如果不刻意区分的话,后续的SMTP实际就是指ESMTP。

SMTP服务器器收到上述消息后,返回本服务器的一些能力信息给客户端:

250-smtp.qq.com
250-PIPELINING
250-SIZE 73400320
250-STARTTLS
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN
250-MAILCOMPRESS
250 8BITMIME

其中最关键的就是STARTTLS能力,也就是该SMTP服务器具备加密连接能力。通常都建议采用加密连接以确保数据安全,毕竟互联网上总有一些黑暗势力在窥探您的信息。

如果客户端不具备SSL/TLS能力,则直接跳到“账户鉴权”流程即可。此时就不能采用PLAIN鉴权模式,必须采用MD5等加密鉴权模式。

2.3 加密连接

既然服务器明确了SSL/TLS能力,因此客户端只要启动SSL/TLS加密TCP连接即可。客户端发送STARTTLS命令,通知服务器准备加密:

STARTTLS

服务器返回响应消息,通知客户端可以开始加密:

220 Ready to start TLS

接下来就是在TCP连接上建立SSL/TLS加密连接,后续通信以后都基于SSL/TLS连接。

请注意,QQMail服务器目前似乎只支持SSLv2、SSLv3加密,不支持TLSv1.0,TLSv1.1和TLSv1.2加密,其他的SMTP服务器(例如outlook、gmail等)则支持全部加密方式。

2.4 第二次握手

加密连接建立后,再次发送EHLO命令进行握手:

EHLO 192.168.1.101

同样,服务器返回相应能力进行响应。此时,通常不再包含STARTTLS指令了:

250-smtp.qq.com
250-PIPELINING
250-SIZE 73400320
250-AUTH LOGIN PLAIN
250-AUTH=LOGIN
250-MAILCOMPRESS
250 8BITMIME

2.5 账户鉴权

鉴权有多种方式,其中PLAIN方式是要求所有SMTP服务器都必须具备的。在未加密的情况下,采用PLAIN方式无疑会泄露账户密码信息。而在已经加密的SSL/TLS连接里,则不存在这个问题,因此我们采用了最简单的PLAIN方式传递账户密码信息进行鉴权。

PLAIN方式很简单,采用”\0user name\0password”进行编码,然后base64转化成文本格式即可。

客户端发送以下鉴权命令:

AUTH PLAIN my_demo_base64_string_here

服务器鉴权成功,返回响应消息:

235 Authentication successful

2.6 提交邮件地址信息

鉴权成功后,客户端可以开始告诉服务器邮件地址信息,例如发送者、接收者等。此类消息不一一累述,请直接参考以下消息流:

MAIL From:<Iloveu@foxmail.com>
250 Ok
RCPT To:<Iloveu2@msn.com>
250 Ok

2.7 准备递交邮件内容

客户端发送DATA命令,告诉服务器准备提交内容:

DATA

服务器返回响应消息,通知客户端可以开始发送数据:

354 End data with <CR><LF>.<CR><LF>

由于后续数据都采用文本编码,因此标准规定用“\r\n.\r\n”标识数据发送结束,也就是SMTP服务器在上述响应消息中指示的“<CR><LF>.<CR><LF>”。

2.8 发送邮件内容

邮件采用文本格式,附件采用base64编码,可以分多次发送,以下为一个简单示例:

From: <Ilovu@foxmail.com>
To: <Iloveu2@msn.com>
Subject: You have a new voice mail: From 301 Day 2015-12-14 Time 10.29.7
MIME-Version: 1.0
Content-type: multipart/mixed; boundary="==491D4D07305F440E009A0125=="

--==491D4D07305F440E009A0125==
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="From 301 Day 2015-12-14 Time 10.29.7.wav"

UklGRpKaAABXQVZFZm10IBIAAAAGAAEAQB8AAEAfA......
--==491D4D07305F440E009A0125==--
.

对于有附件的情况,应在Content-Type中指明boundary参数,例如本例中的“==491D4D07305F440E009A0125==”。而附件内容起始处用“–boundary参数”标识,附件结尾用“–boundary参数–”标识。

发送完数据后,服务器响应消息:

250 Ok: queued as

2.9 结束

发送完邮件后,客户端友好地向服务器告别:

QUIT

服务器也友好地告别:

221 Bye

最后关闭TCP连接,完成整个流程。

推荐一个MSC小工具:mscgen

推荐一个MSC小工具:mscgen

在通信设计中经常需要使用消息序列图(MSC),目前市面上有很多画MSC图的工具,例如UML工具,例如我们自己的一个小工具等等。这些工具都是图形画的工具,而现在要推荐的是mscgen:一个用文字描述然后产生MSC图的工具,能生成SVG、PNG等多种格式。

从该工具网站提供的描述看,语法很简单,很有意思,精确地抓住了MSC图的本质,朴实而实用,非常值得大家尝试使用。

ALG是什么?

ALG是什么?

Application Gateway应用服务器的通称,实际上可以按照网络应用分成不同的种类,例如FTP-ALG、HTTP-ALG等。

这里要说说的是SIP-ALG。这个是通信行当的人才明白的东西,估计大多数人基本不关心。而最近不知道刮什么风,越来越多的路由器里居然都内嵌了SIP-ALG。本来这是个很好的事情,毕竟SIP-ALG能让SIP通话更安全、更能帮助私网的SIP电话进行穿越,实在是有诸多的好处。

可是让人奇怪的是,国内很多路由器的SIP-ALG完全起不了作用,反而引入了各种奇怪的问题。不知道是不是某个路由器通用套件内嵌了这个模块,因此大部分路由器厂商不假思索都自动加持SIP-ALG功能。

如果您的VOIP网络遇到了语音问题,如果您花了很多时间都无法解决,不妨查一下路由器的配置,关掉SIP-ALG功能试试。

并发数

并发数

QQ群中有几位朋友在聊呼叫系统性能的问题,默默地观察了一段时间,感觉大家对一些基本的技术术语其实都没有澄清,比如并发数。

“并发”一般理解为“同时呼叫数”,很多朋友往往将ta误解为“同时试呼数”。“并发呼叫”英文术语是Concurrent Calls(CC),而“同时试呼数”英文术语一般是Calls per second(CPS)。从英文的意思来看其实就更明白一些。

CC和CPS都是衡量呼叫系统性能的重要指标,两者也有一定的联系,这涉及到另一个术语:平均通话时长。通常情况下,根据统计结果,一般呼叫系统中的平均通话时长大约为100秒。当然某些通话时段(例如晚间)、某些特殊人群(比如爱煲电话粥人士)的统计结果有很大差异,但就总体统计而言(尤其是企业通信领域),“100秒”是个相当有代表性的统计结论。

假如“平均通话时长”是100秒,那么CC和CPS的关系就是:CC = 100 × CPS。

例如,有位朋友要求系统能支持100个并发呼叫(CC=100),那么CPS只要1(CPS=100/100)就可以了。也就是每秒只需要支持1个呼叫,这对大多数呼叫系统而言都能轻松支持。

而如果要求能支持到每秒100个呼叫(CPS=100),那么系统资源就必须按照10000(CC=100×100)并发呼叫的容量去设计和考虑。这实际已经是中型呼叫系统的指标了,绝大多数基于Asterisk或者FreeSwitch的小型呼叫系统如果不做特殊修改或者定制,不可能支持这个性能要求。

在没有弄清楚CC和CPS含义的情况下,胡乱提要求或者回答问题是会闹笑话的。比如QQ群里一位大侠吹嘘自己呼叫系统的性能指标,按照上述计算公式,居然可以支持到3亿并发呼叫,也就是说只要四套这个系统,就可以让全中国的人同时打电话!

差点被吓死了。

为什么没有选择sipml5

为什么没有选择sipml5

有多种技术和实现方式可以将SIP与webRTC两个世界连接起来,比如我们的miniWebPhone/miniSIPServer以及sipml5等。当然,最早出现的是sipml5以及与TA配套的webrtc网关。既然已经有了sipml5,为什么我们在设计和实现miniWebPhone(以下简称MWP)时,不采用现成的解决方案呢?

回答这个问题之前,请先粗略的看一下完成后的情况。sipml5的javascript文件大小超过2MB,而MWP的javascript文件是20KB。仅仅对比这两个数据,我就认为我们的决定非常正确,sipml5实在是太臃肿了!

造成sipml5如此庞大的根本原因在于:TA的目标是在浏览器端用javascript来实现一个完整的SIP协议栈及呼叫处理过程。理想很丰满,现实太骨感。

我想sipml5的设计者被HTTP与SIP之间的相似性给迷惑了。两者的确都是基于文本格式,SIP甚至都基本遵循HTTP的消息定义,但是两者却有最根本的区别:HTTP本质上是无状态、无层次的协议,而SIP是有严格的状态,不仅有transaction的状态,也有session和dialog状态。同时SIP又是多层次的,包括transaction、session、UA等不同的层次。当你用一个无状态、无差别的协议模式强行去套一个多状态、多层次的模式,工作量无疑是巨大的。

而对javascript语言而言,其实并不擅长去解析或者分析类HTTP协议格式的文本。而SIP协议虽然采用HTTP协议的文本格式,但是在会话过程中,不仅仅要解析到header层面,还要进一步解析内部各种参数。这种情况就更加不是javascript擅长的。因此可以看到sipml5不得不耗费大量的处理过程去解析SIP协议的细节。javascript擅长处理什么文本格式呢?JSON!因此在miniWebPhone的设计和实现过程中,我们理所当然地采用JSON来重新定义消息格式。

让我们再看看服务器端的设计。这又是另一个让人很纠结的地方。由于浏览器不支持开UDP和TCP连接,只支持websocket连接(本质上其实还是个TCP连接),sipml5的设计者们不得不引入SIP over websocket(这个定义到现在还处于draft状态)。而这要求客户端和服务器两端都必须修改才能支持。虽然websocket与TCP几乎没有区别,但是对SIP协议栈、SIP会话层面的处理来说,可不是仅仅重用TCP处理那么简单,服务器端的工作量同样巨大。

说到这里就稍微跑跑题,让我们先吐槽一下浏览器的实现者们。当浏览器支持websocket的时候,实际上就已经支持了TCP,为什么不向应用层开放TCP连接能力?websocket本质上就是个TCP连接,只有开始的两个握手消息是HTTP格式,后续跟HTTP一点关系都没有。同样,既然已经支持了webRTC,为什么不向应用层开放UDP连接能力?打开一个SRTP端口和打开一个UDP端口同样一点区别都没有。如果浏览器开放了TCP和UDP连接能力,哪怕仅仅开放UDP能力,sipml5的开发者也不用一边哭一边改设计,更不用搞出“SIP over websocket”这么个爷爷不疼、姥姥不爱的东西了。

让我们回到原点。分析了这些困难和不足,既然服务器(或者网关)死活都要修改,那我们为什么不把工作量集中到一端,从而解放另外一端?因此我们放弃sipml5,重新思考:

客户端无疑还是必须基于webRTC和javascript的。但是消息格式不再是HTTP或者SIP格式,而是JSON格式,这样javascript就可以轻松处理。客户端采用无状态方式,呼叫的状态由服务器端来维持。这就是MWP的javascript文件仅仅20KB就ok了的根本原因。

既然客户端采用了JSON格式的消息,因此服务器端也要相应作出设计。主要工作无非就是转码成SIP消息格式并维持websocket连接,其他处理仍然可以沿用目前已有的SIP流程。而我们要做的,仅仅是在客户端和SIP之间做个转换层而已。

大户型路由器

大户型路由器

第一次听到这么个说法,感觉很新奇。于是进一步了解了详情。新闻链接请点击这里

所谓大户型路由器就是信号超级强,以至于隔着几层楼都能有极好的信号。周老板兴致勃勃地说:在我家三楼别墅都能收到信号哟!

差点笑喷!大哥,路由器信号加强了,笔记本、手机等终端的信号怎么解决啊?路由器怎么收三层楼上各类终端wifi信号啊?难不成将笔记本或者手机也改成大户型笔记本、大户型手机?

不是周老板忽悠大家,就是有钱任性被人给忽悠了。

webRTC调试方法小结

webRTC调试方法小结

前段时间完成了miniWebPhone V1版本的开发,基于Chrome浏览器,采用了webRTC技术。在开发过程中,发现其实webRTC技术使用起来还是不太方便,有很多让人感觉很困扰的地方。基本上只有VoIP领域专家才能明白诸多操作以及参数的意义,即便是这样,仍然需要根据Chrome的输出信息来了解Chrome中webRTC的各项细节。

有几种方法可以了解webRTC过程中的细节信息。

方法1:chrome://webrtc-internals/

这个方法是最简单的。在Chrome地址栏中输入上述命令,即可了解webRTC的过程。不过这种方法输出的信息非常粗略。如果您对webRTC很熟悉,那么可以从中了解一些有用的信息。如果您对webRTC不熟悉,那TA的信息您肯定看不明白。

方法2:chrome日志

这种方法我经常使用,而且推荐在linux环境(例如Debian)中使用。实际上,我不知道windows系统下如何看Chrome的日志。Chrome的日志很详细,基本上会输出每个步骤详细的信息。

在linux终端窗口用以下命令启动Chrome即可:

google-chrome --enable-logging=stderr --log-level=4 --vmodule=*libjingle/*=3,*=0

方法3:Chrome源代码

日志可以帮助我们了解大部分webRTC的细节,但是webRTC某些实现仍然是有问题或者说让人困扰的(例如对ICE的处理主、被叫流程不一致,错误处理没有输出日志等),此时直接看代码就是比较好的解决方法。完全、彻底地阅读Chrome是个不可能完成的任务,只能结合Chrome日志去追踪相应的代码。

SIP-INFO传递DTMF信号的若干约定

SIP-INFO传递DTMF信号的若干约定

采用SIP-INFO消息来传递DTMF信号,似乎只是Cisco的定义,没有一个成文的标准,但是目前主流的SIP厂家基本都遵循了相同定义,主要采用‘Signal’参数传递DTMF值:

Signal=1
Duration=160

其中,Signal与DTMF信号对应如下:

DTMF               Signal  
-------------------------
0--9        0--9
*          10
#         11
A--D        12--15
Flash       16

这种映射关系与RFC2833规范一致。但实际上,SIP-INFO既然是文本消息,其实没必要进行转译。例如,传递‘*’信号时,目前的处理是:

Signal=10
Duration=160

这样的定义非常不直观,完全可以直接传递,如下:

Signal=*
Duration=160

SIP-INFO这样传递显得非常直观。RFC2833二进制协议,只能进行定义转换,但是SIP本身是文本协议,足以进行文本性描述。可惜当初不知道为什么非要按照2833方式进行定义,也许这就是为什么这种方式始终没有成为正式规范的原因。

SIP呼叫中的主叫号码

SIP呼叫中的主叫号码

传统电信网的各项规范往往经过了很多专家的讨论以及厂家的验证,因此显得比较严格和规范,例如传统的ISDN规范,定义都很明确。

而因特网的各项规范对比之下就显得很随意,往往是一个规范出来之前就考虑不周全,然后根据情况,又补充出一堆的规范。即使这样,仍然是显得有很多漏洞,或者说有很多不规范、不明确的地方,导致各厂家各说各的道理,给互联互通造成很大的困扰。

当然不是说传统电信规范没有漏洞或者定义含糊的地方,只是相比之下,因特网的规范实在是过于随意。

比如说SIP呼叫中的主叫号码。

在电信网规范中,与主叫相关的号码定义非常明确,主要就这么几个:主叫号码、原主叫号码以及显示号码。各号码的应用场景也非常明确,号码格式中的显示属性等也很明确。

在SIP规范中,与主叫相关的头域有这么几个:From, Contact, P-Asserted-Identity, P-Preferred-Identity, Remote-Party-ID等。这些定义要么没有明确规范好,要么就是多次一举,多半是RFC定义者遇到情况时,拍脑袋一想:算了,加个新的定义搞定吧。结果就让人很无语了。

From和Contact在标准的SIP code规范RFC3261中有明确定义,通常我们都认为From域中携带主叫号码,可惜规范并没有明确限定,因此有一些厂家往往在Contact域中携带主叫号码,而在From域中只携带地址信息。

而显然在实际应用中又遇到一些主叫号码显示的场景(估计主要是电信专家考虑3GPP网络的各项应用时,遇到了与传统主叫号码类业务的冲突),于是乎RFC3325规范就粉墨登场,一举增加了P-Asserted/Preferred-Identity两个头域,也是用来携带主叫号码信息。其中,P-Asserted-Identity主要在信任域的server之间、proxy之间、server与Proxy之间进行传递,而P-Preferred-Identity主要在UA与server/proxy之间传递。看,无聊不?折腾不?

而在正统的P-xxx-Identity头域出来前,民间的野路子显然也遇到了同样的主叫号码类业务的问题,于是乎定义了Remote-Party-ID,并基本参照了ISDN的一些定义,例如号码是否显示等属性,很多SIP厂商已经很high地支持了这个定义,比如说Asterisk。发现没?有了这个定义,还要P-xxx-Identity等定义干什么呢?但是不幸的是P-xxx-Identity已经是正式RFC规范,而Remote-Party-ID还停留在draft-xxx-04阶段(目前已经超时,不知道还会不会升级到正式RFC规范),因此SIP厂商不得不同时支持上述各个定义了。

我有没有提到:有些SIP设备在From/Contact等常见域中根本不携带号码,只在www/proxy-authorization中携带鉴权用户的号码,往往也就是作为主叫号码?

晕倒吧!