Skip to main content

SIP协议入门指南

· 59 min read

前置声明:本文是本人关于 SIP 资料的一个梳理结果,80%的内容都是摘自各个参考资料的一部分,20%是自己在 12-14 年间 SIP 相关工作的理解感悟,然后根据自己拟定的目录大纲进行整合。本文所有版权不归我,如有侵权,请告知,必删。

为什么要写这么一篇文章?

我司(Legrand)核心产品是楼宇视频对讲设备,在业界的名气还不错。

视频通话或者视频会议技术栈通常是 SIP 标准协议,我们当然也不例外。

自从我专做 iOS 开发之后,我就很久不玩 SIP 了,但是这些 SIP 的东西还是没有完全忘记。最近领导给我们开了 Q2 业绩会,提出老旧系统改造,包括 SIP Server 升级,NAT 打洞啊,目的是为了提高系统的稳定性,节省服务器的资源开销。

这些工作跟我没啥关系,因为我不做服务器,但是勾起了我多年的回忆。随即产生一个想法,就是给当年完全不懂 SIP 的自己写一篇入门文章,当做是对自己过去工作、技术的一个总结。

OK,废话不多数,正文开始。

SIP 的概念

SIP,英文全称 Session Initiation Protocol,中文翻译叫会话初始协议,是一个控制发起、修改和终结交互式多媒体会话的信令协议。它是由 IETF(Internet Engineering Task Force,Internet 工程任务组)定义的,最早发布于 1999 年 3 月的 RFC 2543,后来在 2002 年 6 月发布了新的标准 RFC 3261

SIP 是一个基于文本的协议,在这一点上与 HTTP 和 SMTP 相似,易于读取和调试。

SIP URI

我们来对比一个简单的 SIP 请求与 HTTP 请求:

  • SIP: INVITE sip:alice@sipserver.com SIP/2.0
  • HTTP: GET /index.html HTTP/1.1

在 HTTP 中, 请求由三部分组成,GET 指明一个获取资源(文件)的动作,而 /index.html 则是资源的地址,最后是协议版本号。而在 SIP 中,同样也有三部分,INVITE 表示发起一次请求,alice@sipserver.com 为请求的地址,称为 SIP URI,最后也是版本号。

其中,SIP URI 很类似一个电子邮件,其格式为“协议:名称@主机”。与 HTTP 和 HTTPS 相对应,有 SIP 和 SIPS,后者是加密的;名称可以是一串数字的电话号码,也可以是字母表示的名称;而主机可以是一个域名,也可以是一个 IP 地址。

对等协议

SIP 是一个对等的协议,类似 P2P。不像传统电话那样必须有一个中心的交换机,它可以在不需要服务器的情况下进行通信,只要通信双方都彼此知道对方地址(或者,只有一方知道另一方地址)。

例如,bob 的 IP 地址是 192.168.1.10,端口是 5000,alice 的 IP 地址是 192.168.1.11,端口是 6000。当 bob 呼叫 alice 时,他只需要直接呼叫 alice 的 SIP 地址:sip:alice@192.168.1.11:6000,反过来 alice 呼叫 bob 时,只需要呼叫 bob 的 SIP 地址:sip:bob@192.168.1.10:5000

下面是 bob 呼叫 alice 的完整流程:

bob               alice
| |
| INVITE |
|-------------------->|
| 100 Trying |
|<--------------------|
| 180 Ringing |
|<--------------------|
| 200 OK |
|<--------------------|
| ACK |
|-------------------->|
| |
|<---RTP------------->|
|<---RTP------------->|
|<---RTP------------->|
| ... |
| |
| BYE |
|<--------------------|
| 200 OK |
|-------------------->|
| |

UA

在 SIP 网络中,alice 和 bob 都叫做用户代理(UA, User Agent)。UA 是在 SIP 网络中发起或响应 SIP 处理的逻辑功能。UA 是有状态的,也就是说,它维护会话(或称对话)的状态。

UA 有两种功能:一种是 UAC(UA Client 用户代理客户端),它是发起 SIP 请求的一方,如 bob。另一种是 UAS(UA Server),它是接受请求并发送响应的一方,如 alice。由于 SIP 是对等的,如果 alice 呼叫 bob 时(有时候 alice 也主动叫 bob 一起吃饭),alice 就称为 UAC,而 bob 则执行 UAS 的功能。一般来说,UA 都会实现上述两种功能。

Proxy Server

设想 bob 和 alice 是经人介绍认识的,而他们还不熟悉,bob 想请 alice 吃饭就需要一个中间人(M)传话,而这个中间人就叫代理服务器(Proxy Server)。

Redirect Server

还有另一种中间人叫做重定向服务器(Redirect Server),它类似于这样的方式工作 ── 中间人 M 告诉 bob,我也不知道 alice 在哪里,但我老婆知道,要不然我告诉你我老婆的电话,你直接问她吧,我老婆叫 W。这样,M 就成了一个重定向服务器,而他老婆 W 则是真正的代理服务器。这两种服务器都是 UAS,它们主要是提供一对欲通话的 UA 之间的路由选择功能。具有这种功能的设备通常称为边界会话控制器(SBC,Session Border Controller)。

Register Server

还有一种 UAS 叫做注册服务器。试想这样一种情况,alice 还是个学生,没有自己的手机,但它又希望 bob 能随时找到她,于是当她在学校时就告诉中间人 M 说她在学校,如果有事打她可以打宿舍的电话;而当她回家时也通知 M 说有事打家里电话。只要 alice 换一个新的位置,它就要向 M 重新“注册”新位置的电话,以让 M 能随时找到她,这时候 M 就是一个注册服务器。

Alice                          SIP Server
| |
| REGISTER |
|------------------------------->|
| SIP/2.0 401 Unauthorized |
|<-------------------------------|
| REGISTER |
|------------------------------->|
| SIP/2.0 200 OK |
| |

B2BUA

最后一种叫做背靠背用户代理(B2BUA,Back-to-Back UA)。需要指出,其实 RFC 3261 并没有定义 B2BUA 的功能,它只是一对 UAS 和 UAC 的串联。FreeSWITCH 就是一个典型的 B2BUA,事实上,B2BUA 的概念会贯穿本书始终,所以,在此我们需要多花一点笔墨来解释。

我们来看上述故事的另一个版本:M 和 W 是一对恩爱夫妻。M 认识 bob 而 W 认识 alice。M 和 W 有意搓合两个年轻人,但见面时由于两人太腼腆而互相没留电话号码。事后 bob 相知道 alice 对他感觉如何,于是打电话问 M,M 不认识 alice,就转身问老婆 W (注意这次 M 没有直接把 W 电话给 bob),W 接着打电话给 alice,alice 说印象还不错,W 就把这句话告诉 M, M 又转过身告诉 bob。 M 和 W 一个面向 bob,一个对着 alice,他们两个合在一起,称作 B2BUA。在这里,bob 是 UAC,因为他发起请求;M 是 UAS,因为他接受 bob 的请求并为他服务;我们把 M 和 W 看做一个整体,他们背靠着背(站着坐着躺着都行),W 是 UAC,因为她又向 alice 发起了请求,最后 alice 是 UAS。其实这里 UAC 和 UAS 的概念也不是那么重要,重要的是要理解这个背靠背的用户代理。因为事情还没有完,bob 一听说 alice 对他印象还不错,心花怒放,便想请 alice 吃饭,他告诉 M, M 告诉 W, W 又告诉 alice,alice 问去哪吃,W 又只好问 M, M 再问 bob…… 在这对年轻人挂断电话这前, M 和 W 只能“背对背”的工作。

背靠背呼叫流程图如下:

                       FreeSWITCH
Alice (B2BUA Server) Bob
| | | |
| INVITE F1 | | |
|------------------->| | |
| 100 Trying F2 | | |
|<-------------------| | INVITE F3 |
| | |------------------->|
| | | 100 Trying F4 |
| | |<-------------------|
| | | 180 Ringing F5 |
| 180 Ringing F6 | |<-------------------|
|<-------------------| | |
| | | 200 OK F7 |
| 200 OK F8 | |<-------------------|
|<-------------------| | ACK F9 |
| ACK F10 | |------------------->|
|------------------->| | |
| RTP Media | | RTP Media |
|<==================>| |<==================>|
| BYE F11 | | |
|------------------->| | BYE F12 |
| 200 OK F13 | |------------------->|
|<-------------------| | 200 OK F14 |
| | |<-------------------|
| | | |

从上图可以看出,四个人其实全是 UA。从上面故事可以看出,虽然 FreeSWITCH 是 B2BUA,但也可以经过特殊的配置,实现一些代理服务器和重定向服务器的功能,甚至也可以从中间劈开,两边分别作为一个普通的 UA 来工作。这没有什么奇怪的,在 SIP 世界中,所有 UA 都是平等的。具体到实物,则 M 和 W 就组成了实现软交换功能的交换机,它们对外说的语言是 SIP,而在内部,它们则使用自己家的语言沟通。bob 和 alice 就分别成了我们常见的软电话,或者硬件的 SIP 电话。

回铃音与 Early Media

A  ------ |a 交换机 | ---X--- | 交换机 b| -------- B

为了便于说明,我们假定 A 与 B 不在同一台服务器上(如在 PSTN 通话中可能不在同一座城市),中间需要经过多级服务器的中转。

假设上图是在 PSTN 网络中,A 呼叫 B,B 话机开始振铃,A 端听回铃音(Ring Back Tone)。在早期,B 端所在的交换机只给 A 端交换机送地址全(ACM)信号,证明呼叫是可以到达 B 的,A 端听到的回铃音铃流是由 A 端所在的交换机生成并发送的。但后来,为了在 A 端能听到 B 端特殊的回铃音(如“您拨打的电话正在通话中…” 或 “对方暂时不方便接听您的电话” 尤其是现代交换机支持各种个性化的彩铃 - Ring Back Color Tone 等),回铃音就只能由 B 端交换机发送。在 B 接听电话前,回铃音和彩铃是不收费的(不收取本次通话费。彩铃费用一般是在 B 端以月租或套餐形式收取的)。这些回铃音就称为 Early Media(早期媒体)。它是由 SIP 的 183(带有 SDP)消息描述的。

理论上讲,B 接听电话后交换机 b 可以一直不向 a 交换机发送应答消息,而将真正的话音数据伪装成 Early Media,以实现“免费通话”。

INVITE 消息示例

下面是一个完整的 INVITE 消息的数据内容,应该很好理解。整个消息就两个部分:Header + SDP,其中 Header 跟 HTTP 的 Header 没啥两样,SDP 也完全就可以当做 HTTP 的 Body 来看,两者使用换行来分割。

INVITE sip:some@192.168.31.131:50027 SIP/2.0
Via: SIP/2.0/UDP 192.168.31.131:51971;rport;branch=z9hG4bKiYblddPPX
Max-Forwards: 70
To: <sip:some@192.168.31.131:50027>
From: <sip:null@null>;tag=Prf3c3Xc
Call-ID: cenXTa4i-1423587756904@appletekiAir
CSeq: 1 INVITE
Content-Length: 215
Content-Type: application/sdp
Contact: <sip:null@192.168.31.131:51971;transport=UDP>

v=0
o=user1 685988692 621323255 IN IP4 192.168.31.131
s=-
c=IN IP4 192.168.31.131
t=0 0
m=audio 49432 RTP/AVP 0 8 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:101 telephone-event/8000
a=sendrecv

空行之后的就是 SDP 内容,描述一些音视频信息。

SIP 的优点

SIP 的优点是简单灵活。

尤其对开发人员来说很友好,你能看得懂 HTTP 协议,基本上就能很快理解 SIP 协议,全赖于它基于文本的协议。

SIP 非常容易扩展,RFC 3261 的核心内容其实并不多。

SIP 既不是会话描述协议,也不提供增加供会议控制功能。

为了描述消息内容的负载情况和特点,SIP 使用 SDP 来描述终端设备的特点。SIP 自身也不提供服务质量 (QoS),它与负责语音质量的资源保留设置协议 (RSVP) 互操作。它还与若干个其他协议进行协作,包括负责定位的轻型目录访问协议 (LDAP)、负责身份验证的远程身份验证拨入用户服务 (RADIUS) 以及负责实时传输的 RTP 等多个协议。

应用场景

SIP 的应用场景有很多,比如交互式游戏、音乐、视频点播,音视频会议通话,PPT 展示,呼叫中心,语音短信,IM 等等。

开源解决方案

目前 SIP 相关的开源方案基本上都是 C 语言写的,一是为了跨平台、要兼容小设备,第二个是大部分开源作者本身是从事通信、嵌入式相关工作的,他们的背景决定了当前的语言选择。从作者们写下第一行代码开始,这些开源方案都有 10 年多、甚至 20 年的开发时间。

下面我们分别从服务端和客户端两个方向来介绍一下当前主流的开源方案。

服务端方案

服务端的开源方案有很多,老牌知名的 Asterisk,后起之秀 FreeSWITCH,代理界扛把子 Kamailio。

本文只介绍 FreeSWITCH 和 Kamailio。

FreeSWITCH

FreeSWITCH 是一个开源的电话交换平台,支持 Windows、Linux、Mac 等平台。

功能

FreeSWITCH 支持下面的这些标准协议:

  • SIP 信令
  • SIP 终端
  • H323 终端
  • App
  • WebRTC
  • RTMP

首先 FreeSWITCH 支持 SIP 信令,就是音频和视频通话标准的协议,支持各种客户端、终端,目前市面上很多的会议设备都是支持 SIP 的,可以直接实现互通。

随着 WebRTC 的发展,很多人开始将其移植到手机客户端上。WebRTC 的优点在于不需要自己写媒体层,随着 WebRTC 的开源带来了很多特性,比如说 JitterBuffer、回声消除、降噪等等之类的内容在 WebRTC 中已经包含,不需要再自己写,虽然不如各种私有化的厂家做的好,因为私有化的厂家可以进行更加极致的优化。但对于一个开源项目来说,WebRTC 做的已经足够好了,由于 WebRTC 只有媒体层没有信令层,所以大家都开始往 WebRTC 上套各种信令,其中就有 SIP 应用得最多。

值得一提的是 RTMP,尽管目前 Flash 基本上已经没有人用了,但 RTMP 协议还是非常好的,目前更广泛应用于直播和推流等。

愿景

FreeSWITCH 致力于做一个软交换,它建立于一个坚实的核心上面,由一个有限状态机驱动。 该项目的目标包括稳定,可伸缩性,以及抽象性。

版本
  • 2006 年 FreeSWITCH 发布了第 1 个版本。FreeSWITCH 本身最开始是作为一个 PBX 进行的。PBX 就是我们所说的企业里的交换机,打电话用的。

  • 2008 年发布了 1.0 版本—凤凰版,像凤凰涅磐一样,经历了无数次的崩溃、优化,所以称为凤凰版。

  • 2012 年发布 FreeSWITCH1.2 版本(FreeSWITCH 的版本号都是偶数),1.2 版本非常稳定,音频方面也已经非常成熟,在电话方面基本上没什么可做的了。但随着的 WebRTC 的出现,FreeSWITCH 决定接下来要支持 WebRTC。

  • 2014 年推出 1.4 版本,开始支持 WebRTC。早期的 WebRTC 还不是很稳定,但 WebRTC 的标准变得非常快,所以 FreeSWITCH 也一直在跟着改。

  • 2015 年 1.6 版本增加了视频转码和视频会议支持,其它的如视频 Jitter Buffer 以及 RTCP 控制功能是新加的。所以,如果你做视频应用的话,肯定应该选择该版本之后的了。

  • 2017 年 1.8 版本增加了基于 Session 的实时文本聊天,将可以通过 RTP 传输,也可以通过 MSRP 传输,以及在 Verto 模块中通过 Websocket 传输。

  • 2017 年之后核心模块没有变动过,即使 2019 年发布 1.10 版本,也只是优化了一下数据库部分。

  • 再到最新 2021 年的 1.10.7 版本,这几年一直在不停的修 bug,将 FreeSWITCH 做得越来越完善。

核心团队在 2018 年创立了 SignalWire 公司,提供商业化的 FreeSWITCH 支持,并为开源项目提供永久性商业赞助。

架构

6klW4x

它使用一种模块化、可扩展性的结构,只有必需的功能和函数才会加入到内核中,从而保证了其稳定。作为一款开源软件,它最大的好处就是你可以拿过来自己编译进行,并根据你的需要来开发自己的模块。

FreeSWITCH 使用线程模型来处理并发请求,每个连接都在单独的线程中进行处理。这不仅能提供最大强度的并发,更重要的是,即使某路电话发生问题,也只影响到它所在的线程,而不会影响到其它电话。

FreeSWITCH 的核心非常短小精悍,这也是保持稳定的关键。所有其它功能都在外围的模块中。模块是可以动态加载(以及卸载)的,在实际应用中可以只加载用到的模块。外围模块通过核心提供的 Public API 与核心进行通信,而核心则通过回调机制执行外围模块中的代码。

核心 FS Core 是 FreeSWITCH 的核心,它包含了关键的数据结构和复杂的代码,但这些代码只出现在核心中,并保持了最大限度的重用。外围模块只能通过 API 调用核心的功能,因而核心运行在一个受保护的环境中,核心代码都经过精心的编码和严格的测试,最大限度地保持了系统整体的稳定。

核心代码保持了最高度的抽象,因而它可以调用不同功能,不同协议的模块。同时,良好的 API 也使得编写不同的外围模块非常容易。

数据库 FreeSWITCH 的核心除了使用内部的队列、哈希表存储数据外,也使用外部的 SQL 数据库存储数据。当前,系统的核心数据库使用 SQLite,默认的存储位置是 db/core.db 。 使用外部数据库的好处是--查询数据不用锁定内存数据结构,这不仅能提供性能,而且降低了死锁的风险,保证了系统稳定。命令 show calls、show channels 等都是直接从数据库中读取内容并显示的。由于 SQLite 会进行读锁定,因此不建议直接读取核心数据库。

系统对数据库操作做了优化,在高并发状态时,核心会尽量将几百条 SQL 一齐执行,这大大提高了性能。但在低并发的状态下执行显得稍微有点慢,如一个 channel 已经建立了,但还不能在 show channels 中显示;或者,一个 channel 已经 destroy 了,还显示在 show channels 中。但由于这些数据只用于查询,而不用于决策,所以一般没什么问题。

除核心数据库外,系统也支持使用 ODBC 方式连接其它数据库,如 PostgreSQL、MySQL 等。某些模块,如 mod_sofia、mod_fifo 等都有自己的数据库(表)。

目录结构 在 *nix 类系统上,FreeSWITCH 默认的安装位置是 /usr/local/freeswitch,在 Windows 上可能是 C:\freeswitch,目录结构大致相同。

bin         可执行程序
db 系统数据库(sqlite),FreeSWITCH 把呼叫信息存放到数据库里以便在查询时无需对核心数据结构加锁
htdocs HTTP Web srver 根目录
lib 库文件
mod 可加载模块
run 运行目录,存放 PID
sounds 声音文件,使用 playback() 时默认的寻找路径
grammar 语法
include 头文件
log 日志,CDR 等
recordings 录音,使用 record() 时默认的存放路径
scripts 嵌入式语言写的脚本,如使用 lua()、luarun()、jsrun 等默认寻找的路径
storage 语言留言(Voicemail)的录音
conf 配置文件,详见下节

配置文件 配置文件由许多 XML 文件组成。在系统装载时,XML 解析器会将所有 XML 文件组织在一起,并读入内存,称为 XML 注册表。这种设计的好处在于其非常高的可扩展性。由于 XML 文档本身非常适合描述复杂的数据结构,在 FreeSWITCH 中 就可以非常灵活的使用这些数据。并且,外部应用程序也可以很简单地生成 XML,FreeSWITCH 在需要时可以动态的装载这些 XML。另外,系统还允许在某些 XML 节点上安装回调程序(函数),当这些节点的数据变化时,系统便自动调用这些回调程序。

安装

在 Debian 11 或 Centos 7 上可以直接通过 apt-get 或 yum 来安装,Windows 有打包好的 msi 安装文件。 在实际安装过程中,我们尽量选用比较新的版本,FreeSWITCH 相对还是很稳定的。

FreeSWITCH 默认设置了 20 个用户(1000-1019),密码默认 1234。如果你需要更多的用户,或者想通过添加一个用户来学习 FreeSWITCH 配置,只需要简单执行以下三步:

  • 在 conf/directory/default/ 增加一个用户配置文件
  • 修改拨号计划(Dialplan)使其它用户可以呼叫到它
  • 重新加载配置使其生效

当然,现在流行 Docker 安装,FreeSWITCH 也维护了一个镜像

这里推荐另一个 Docker 镜像xswitch-free,相比官方的会更简单一些。

GUI 配置工具

FreePBX 具有很多灵活功能,包括自定义安装高级配置工具等

FusionPBX 是一个支持多平台,开源的 FreeSWITCH WEB 界面,它基于 BSD 许可证发布。它已证明是一个可定制的、非常灵活的 WEB 界面。它的后台数据库支持 SQLite、PostgreSQL、MySQL 等。它运行起起非常稳定,从小型的到大型的环境中都已经有所应用。

Sofia-sip

Sofia-SIP 是由诺基亚公司开发的 SIP 协议栈,它以开源的许可证 LGPL 发布,为了避免重复发明轮子,FreeSWITCH 便直接使用了它。

在 FreeSWITCH 中,实现一些互联协议接口的模块称为 Endpoint。FreeSWITH 支持很多的 Endpoint, 如 SIP、H232 等。那么实现 SIP 的模块为什么不支持叫 mod_sip 呢?这是由于 FreeSWITCH 的 Endpoint 是一个抽象的概念,你可以用任何的技术来实现。实际上 mod_sofia 只是对 Sofia-SIP 库的一个粘合和封装。除 Sofia-SIP 外,还有很多开源的 SIP 协议栈,如 pjsip、osip 等。

最初选型的时候,FreeSWITCH 的开发团队也对比过许多不同的 SIP 协议栈,最终选用了 Sofia-SIP。FreeSWITCH 是一个高度模块化的结构,如果你不喜欢,可以自己实现 mod_pjsip 或 mod_osip 等,它们是互不影响的。这也正是 FreeSWITCH 架构设计的精巧之处。

背后有趣的故事

FreeSWITCH 的作者 Anthony Minissale 曾经是 Asterisk 的开发者。

FreeSWITCH 的核心团队是非常包容的,这对开源项目来说非常有帮助。例如你有个官方目前不支持的需求,你可以提出你的想法或实现方法,如果没太大的兼容问题,大部分时候核心团队把你的 PR 合并到主分支。

mod_yaml 的故事

当年,有人说,FreeSWITCH 为什么要用 XML 做为配置文件,真麻烦,为什么不用 YAML,配置起来多简单啊。那个人甚至说到:

如果 FreeSWITCH 支持使用 YAML 配置,从它支持之日起我发誓我再也不用 Asterisk 了。

猜猜 Anthony 是怎么回应的?

好吧,你不要食言。请删除你所有的 Asterisk 并且安装 mod_yaml,…… 我从来没听说过 YAML,但我还是花了 3 个小时完成了这个模块,我想我任何时候都不想使用这个模块,但是,为了你,我还是写了,……如果我第一次去你家吃饭,告诉你我不喜欢你墙壁的颜色?……然后,我走了,你一年再也没有见到我(意思是说,总是有人在 FreeSWITCH 里要这功能要那功能,等该功能真有了,最初要求该功能的人却不见了)。

其实呢,Anthony 的意思很明白,以他多年的经验,他本身对软件的设计有自己的见解,如果你的想法就是与他不同,他也不会完全反对,但是,你要自己证明你说的确实有用。另外,FreeSWITCH 是开源软件,但 FreeSWITCH 的开发者不是活菩萨,不能有求必应。如果你想要个什么特殊的功能,可以,但是,至少在该功能上你要向 FreeSWITCH 提供帮助,有钱出钱有力出力,以便大家帮你把梦想实现。

FS-353 的故事

可以算得上是 FreeSWITCH 史上最大的争论了,该争论的主要内容是,FreeSWITCH 用到了好多第三方的库,那么,这些库的代码到底应该是放在 FreeSWITCH 的代码库中,静态编译连接,还是应该动态连接操作系统上提供的库。

早期 FreeSWITCH 是把所有第三方库源码都打包了一份到源码库里,静态链接第三方库。这样做的好处就是只需要编译一份代码,不依赖其他系统库了,免了很多库版本不一致、运行期不同的麻烦。但是坏处也是相对的,首先各个 LInux 发行版,比如 Debian,不允许有重复的库,FreeSWITCH 发布包将无法直接包括在操作系统的发行版里。这样,你就无法通过 apt-get 或 yum 来安装了。

到底是静态库还是动态库?核心团队和开发者们进行了非常激烈的讨论,这是一项将会花很大的精力的工程,光靠核心团队无法完成这项工作,需要社区的帮助,这些帮助需要实实在在的提供解决方案,而不是只提需求和建议。

后来,随着时间的推移,FreeSWITCH 社区中还是有人将 FreeSWITCH 打包到各 Linux 发行套件的发行版中。因而,即使没有人站出来说要对 FS-353 负责,FreeSWITCH 社区也挑起了这杆大旗。在这方面,最典型的代表就是 Travis Cross。Travis 也是 FreeSWITCH 社区里的专家,在 FreeSWITCH 代码库维护、安全及跨平台编译方面都有很大贡献。在他的推动下,在 FreeSWITCH 开发者多次开会之后,终于决定了不再坚持把其它各种库的源代码再放到 FreeSWITCH 代码库中,而是使用系统上已有的库。

Kamalio/OpenSIPS

Kamailio(起源于 SER)是一个开源的 SIP 服务器,主要用作 SIP 代理服务器、注册服务器等,即只处理信息,不处理媒体。

Kamailio 最初的代码从 2001 年开始开发,至今已经有 20 多年的历史了。开始是德国人写的,所以我说德国在 SIP 上的技术储备非常厉害。后来团队闹矛盾,有些人走了,创立了 OpenSIPS。

Kamailio 主要处理 SIP 协议,因此,对 SIP 协议有较好地了解有助于更快地学习 Kamailio。反过来,学好 Kamailio 又有助于进一步了解 SIP 协议,两者相辅相成的。

Kamailio 基于 GPLv2+开源协议发布,它可以支持每秒钟成千上万的呼叫建立和释放(高 CAPS,Call Attempt Per Second),可用于构建大型的 VoIP 实时通信服务——音视频通信、状态呈现(Presence)、WebRTC、实时消息等;也可以构建易扩容的 SIP-to-PSTN 网关、IP-PBX 系统以及连接 Asterisk™、FreeSWITCH™、SEMS 等。

Kamailio 支持异步的 TCP、UDP、SCTP、TLS、WebSocket,支持 WebRTC,支持 IPv4 和 IPv6,支持 IM 消息及状态呈现,支持 XCAP 和 MSRP Relay,支持异步操作,支持 VoLTE 相关的 IMS 扩展,支持 ENUM、DID 以及 LCR 路由,支持负载均衡、主备用路由(Fail-Over),支持 AAA(记账、鉴权和授权),支持很多 SQL 和 noSQL 数据库后端如 MySQL、PostgreSQL、Oracle、Radius、LDAP、Redis、Cassandra、MongoDB、Memcached 等,支持消息队列如 RabbitMQ、Kafka、NATS 等,支持 JSON-RPC 和 XML-RPC 控制协议以及 SNMP 监控等诸多特性。

简单来说,Kamailio 是一个:

  • SIP Server,SIP 服务器
  • SIP Proxy Server,SIP 代理服务器
  • SIP Registrar Server,SIP 注册服务器
  • SIP Location Server,SIP 地址查询服务器
  • SIP Redirection Server,SIP 重定向服务器
  • SIP Application Server,SIP 应用服务器
  • SIP Loadbalance Server,SIP 负载均衡服务器
  • SIP WebSocket Server,SIP WebSocket 服务器
  • SIP SBC Server,SIP SBC 服务器

相对而言,Kamailio 不是—个

  • SIP Phone,SIP 软电话
  • Media Server,媒体服务器
  • B2BUA,背靠背用户代理

它的特性是:快、可靠、灵活。

但它不发起通话,不应答电话,不做音、视频等媒体处理。

下图是官方的整体架构图:

rgyCg1

SIP 集群

先来明确几个知识点:

  • SIP 代理的作用是提供 SIP 信令的入口、授权、访问控制,位置存储、媒体路由和媒体的负载功能;
  • SIP 代理的主要开源产品有:opensips、kamailio、opensers,单台 sip 代理服务器能注册 1W 的用户;
  • 媒体服务器主要提供媒体协商、转码和 RTP 数据交换功能;
  • 媒体服务器的主要开源产品有 Asterisk、FreeSWITCH,都是擅长做媒体软交换;

在 SIP 集群这块的资料很少,目前了解到只有OV500 这个项目,是非常典型的 Kamailio + FreeSWITCH 解决方案。

OV500 的架构图如下:

MS1PHi

OV500 将 Kamailio 当 SIP 代理服务器和 RTP 代理服务器,用 FreeSWITCH 集群处理通话功能。

一般有两种配套方案:

  • 单 Kamailio + N 个 FreeSWITCH 实例:适合中小规模用户的场景
  • 多 Kamailio + N 个 FreeSWITCH 实例:适合海量用户的场景

客户端方案

Linphone

目前最流行的 SIP 开源客户端,现如今做 SIP 的基本上都用他家的客户端做测试,或者直接集成 Linphone SDK 到自己的 App 上。

Linphone 支持 iOS、Android、Mac、Windows、Linux,既有 GUI 端,也有 Console 端,这也是很多人选择它的原因吧。

Linphone 的开源协议是 GNU GPLv3 ,你可以免费用源代码,但是改了它的代码你也要开源,当然它还有付费的服务。

Linphone 的核心是Liblinphone,底层是 C 语言写的,上层应用基于不同平台封装了不同的接口,比如桌面端有 C++封装接口,iOS 端是 Swift 封装接口,Android 是 Java 封装接口等。

Liblinphone 的核心组件有三个:

  • Mediastreamer2

    处理音视频,为了解决回音消除的问题,在 2013 年就整合了 WebRTC 的 AEC 算法

  • oRTP RTP 库

  • belle-sip

    早期使用的是oSIP,后来 Linphone 自己重写了协议栈,取名 belle-sip

来看一张官方整体架构图:

QHrb2F

Linphone 的 SIP 服务器使用的是自家的Flexisip,其他公司使用 Flexisip 的例子没见到。但它也有自己的特色,其中之一是用 C++14 写的,其他大部分都是 C 语言写的。还有就是支持 HTTP2,包含了一个推送网关,可以使用 SIP 来推送消息或拨打电话。

Linphone 的源码托管在 Gitlab 云端,GitHub 上也有镜像,放在BelledonneCommunications组织下。

我认为 Linphone 最好的点是在于提供了一个非常完整的移动端 App,而且 App 用的就是 Liblinphone 接口,并没有做一些魔改操作。

所以当我们自己集成 Liblinphone 时,遇到问题就可以对照一下官方的 App 是怎么做的,如果同样版本的 Liblinphone 编译出来的 App,却发现你的 App 有问题,官方 App 没有问题,那大概率是你没有配置好,或者用错了接口。

另外一个点就是 Liblinphone 会有一个名为linphonerc-factory`配置文件,里面有很多参数可以设置,当你使用 console 调试的时候非常方便,改了参数重新启动即可,无需编译了。尤其是在遇到一些音频的问题时,比如回音、噪音、啸叫等问题时,不同的麦克风会有不一样的坑,需要具体调试参数。


回声消除
echocancellation=1:回声消除这个必须=1,否则会听到自己说话的声音
ec_tail_len= 100:尾长表示回声时长,越长需要cpu处理能力越强
ec_delay=0:延时,表示回声从话筒到扬声器时间,默认不写
ec_framesize=128:采样数,肯定是刚好一个采样周期最好,默认不写

回声抑制
echolimiter=0:等于0时不开会有空洞的声音,建议不开
el_type=mic:这个选full 和 mic 表示抑制哪个设备
eq_location=hp:这个表示均衡器用在哪个设备
speaker_agc_enabled=0:这个表示是否启用扬声器增益
el_thres=0.001:系统响应的阈值 意思在哪个阈值以上系统有响应处理
el_force=600 :控制收音范围 值越大收音越广,意思能否收到很远的背景音
el_sustain=50:控制发声到沉默时间,用于控制声音是否拉长,意思说完一个字是否被拉长丢包时希望拉长避免断断续续

降噪
noisegate=1 :这个表示开启降噪音,不开会有背景音
ng_thres=0.03:这个表示声音这个阈值以上都可以通过,用于判断哪些是噪音
ng_floorgain=0.03:这个表示低于阈值的声音进行增益,用于补偿声音太小被吃掉

网络抖动延时丢包
audio_jitt_comp=160:这个参数用于抖动处理,值越大处理抖动越好,但声音延时较大 理论值是80根据实际调整160
nortp_timeout=20:这个参数用于丢包处理,值越小丢包越快声音不会断很长时间,同时要跟el_sustain配合声音才好听

PJSIP

早期 pjsip 非常简陋,代码还是 SVN 维护的,支持的平台比较少,官方都没有支持 Android、iOS,都是其他开源爱好者贡献的。当初项目中没有选择它的原因是视频部分一直没做好,等不及官方的开发计划,太拖沓了。

我们来回顾一下它的版本历史,从 2.0 开始吧,再早之前就没意义了:

版本发布日期主要更新
2.02012.5支持视频,只限桌面平台,移动端只能呵呵了
2.12013.31. STUN、TURN、ICE 支持,分离了 pjnath 库专门用来穿透;2、支持微软的 SILK 音频编解码器,以及 OpenCore AMR-WB
2.22014.2在 C 的接口上封装一层 C++接口,取名 PJSUA2,方便 C++、Java、Python 等面向对象的语言调用
2.32014.9支持 iOS 的视频,我的项目都做完了才发布,气人
2.42015.4支持 Android 的视频,夹带私货支持 bdIMAD 的 AEC 功能
2.4.52015.10支持视频采集方向
2.52016.5支持 WebRTC AEC,太慢了
2.5.52016.7支持 IPv6,DNS 解析
2.62017.1支持 Windows 的 UWP 和 Opus 编解码器
2.72017.9支持 DTLS-SRTP,iOS 和 Mac 的原生 H.264 编码
2.82018.9支持 WebRTC 互操作性 的 RTP/SAVPF 和 SSRC
2.92019.6支持视频会议
2.102020.2支持 WebRTC 互操作性的 RTCP-FB PLI,VP8/VP9 视频编解码器
2.112021.3支持 Trickle ICE,iOS 原生 SSL,Android 原生编解码(H264, VP8, VP9, AMR-NB & AMR-WB),增加了 iOS Swift 和 Android Kotlin 的例子 app
2.122022.2更新 WebRTC 最新的 AEC3 和 AGC2 算法,支持 Android 的 Oboe(Oboe 是 Google 的)

整整 10 年时间,每年都发布一个 minor 版本。看起来更新很多,但我感觉还是太慢了。比如在 WebRTC 的支持上就比 Linphone 慢了 2 年时间。

个中原因也很简单,核心开发者才 5 个人,其中创始人 Benny 在 2016 年左右离开了队伍,目前主要的贡献者就 3 个人,其他都是打酱油的。

GitHub 仓库截止到 2022-06-11,有268个 issue,Bug 太多了。如果你的项目里选择 pjsip 方案,一定要有能力非常强的人才行,不然就是个大坑。

Doubango

Doubango 是一个法国的黑人小伙写的,非常厉害,一个人把所有端都写了。Doubango 除了支持三大 PC 客户端,还支持 iOS、Android、Windows Phone、WebRTC 等,这在 2012-2014 年间是比较少有的。

当初看中 Doubango 有几个原因,除了基本的 SIP 通信、音视频外,还有以下这些:

  1. 支持RFC 3966,可以使用tel://10086这样拨打电话。
  2. 支持IPSec VPN,在网络层的安全通信方案,直接运行在 IP 层,比 SSL VPN 的应用层更安全。
  3. 支持 DTLS-SRTP ,继续增强安全性
  4. 支持 DTMF,可以In Band或者Out Band传输

DTLS(数据报安全协议) 基于 UDP 场景下数据包可能丢失或重新排序的现实情况,为 UDP 定制和改进的 TLS 协议。DTLS 提供了 UDP 传输场景下的安全解决方案,能防止消息被窃听、篡改、身份冒充等问题。 SRTP 就是 RTP 的加密版本 SRTP 要解决的问题:对 RTP/RTCP 的负载 (payload) 进行加密,保证数据安全; 保证 RTP/RTCP 包的完整性,同时防重放攻击。

下图是 Doubango 的整体架构,层次设计很好,tiny 开头的库都是作者新写的,很少引用第三方库,非常牛逼。 Doubango_Layers.png

现在在 github 上看到最后的 commit 停留在 2019 年,作者已不再维护了。

NAT 穿透

NAT 介绍

NAT 技术(Network Address Translation,网络地址转换)是一种把内部网络(简称为内网)私有 IP 地址转换为外部网络(简称为外网)公共 IP 地址的技术,它使得一定范围内的多台主机只利用一个公共 IP 地址连接到外网,可以在很大程度上缓解了公网 IP 地址紧缺的问题。

另外,NAT 还有基于安全性的考虑,NAT 会禁止非主动发出的请求进入,比如防止黑客直接攻击你。

为什么要穿透?

假如每个人有一个唯一 IP,通过 IP 就能找到对方,典型就是公司内网等局域网,那么完全不需要穿透,因为你就可以直接跟对方建立连接了。另外就是通信双方都有独立的公网 IP 地址,也可以直接通过公网 IP 地址通信。

在我们的现实的应用环境中,大多数设备都是位于 NAT 之后,比如连着同一个基站的移动设备,同一个小区的宽带用户等,NAT 的存在使得设备间不能直接进行点对点的通信。尤其是 IoT,视频通话等场景。

这时候我们就需要用到 NAT 穿透技术,这是为了解决使用了 NAT 后的私有网络中设备间建立连接的问题。

SIP 中用到 NAT 穿透就是在 A 和 B 之间建立直接的媒体通道,无需经过服务器转发。这里注意是媒体部分,不是 SIP 信令。这样做的目的是为了降低服务器的压力,说白了就是省带宽、CPU 等成本资源。

主流穿透方案

目前主流的穿透方案就是:STUN,TURN,ICE。其实这三者可以看做一个整体,实际应用下基本上三个都有用到。

这里简单介绍一下概念,不做详细说明了。关于这三个技术的 RFC 放在参考资料,可以对应查找。

STUN

C-S 协议,简单的 UDP 穿透 NAT

TURN

TURN 与 STUN 的共同点都是通过修改应用层中的私网地址达到 NAT 穿透的效果,不同点是 TURN 是通过两方通讯的“中间人”方式实现穿透。

ICE

ICE 是一组穿透方法而不是协议,它融合了 STUN 和 TURN,ICE 使得两个 NAT 后设备通信更加便捷,ICE 使用 STUN 进行打洞,若失败,则使用 TURN 进行中转。

如果A想与B通信,那么ICE过程如下:

  1. A 收集所有的 IP 地址,并找出其中可以从 STUN 服务器和 TURN 服务器收到流量的地址;
  2. A 向 STUN 服务器发送一份地址列表,然后按照排序的地址列表向 B 发送启动信息,目的是实现节点间的通信;
  3. B 向启动信息中的每一个地址发送一条 STUN 请求;
  4. A 将第一条接收到的 STUN 请求的回复信息发送给 B;
  5. B 接到 STUN 回复后,从中找出那些可在 A 和 B 之间实现通信的地址;
  6. 利用列表中的排序列最高的地址进一步的设备间通信。

fmgQ4j

三者的特性对比

特性STUNTURNICE
实现复杂度实现简单难于实现。TURN 的安全性设计增加终端设置的复杂度一般
TCP 穿透支持不支持支持支持
对现有设备的要求要求客户端支持,对现有 NAT 设备无改动要求,需增加 STUN 服务器对现有 NAT 设备无要求,要求客户端支持,需增加 TURN 服务器 s对 NAT 设备无要求,支持所有类型的 NAT 设备。客户端必须支持,网路结构中需增加 STUN/TURN 服务器
可扩展性可扩展性好,与具体协议无关可扩展性好,与具体协议无关可扩展性好,与具体协议无关
安全性一般一般较好
健壮性差,不支持 symmentric 型 NAT好,支持所有类型的 NAT好,适用与所有 NAT 及 NAT 拓扑类型,且由于存在中继服务器,NAT 穿透一般总是能成功
其他支持自动检测 NAT 类型,使用户即使在使用 STUN 协议无法实现 NAT 穿透时还可以根据 NAT 类型自主选择其他可使用的 NAT 穿透方案与 P2P 穿透方式相比,性能时 relay 穿透方式的弱点。另外 TURN 无法实现负载分担,解决的方式是把 media relay 服务器的分配工作放在 SIP proxy 完成
Trickle ICE

在 WebRTC 中,使用 ICE 框架进行 P2P 通信。前面说到 ICE 中第一步是收集 candidate,需要遍历 STUN 以及 TURN 服务器,这一步需要耗费很多时间,导致双方建立通信时间很慢。为了解决这一问题,WebRTC 引入了 Trickle ICE,这样 candidate 收集以及连通性检查可以同时进行,加快双方建立通信的速度。

流程如下:

开源库

若要让我们的程序支持 ICE,我们可以借助第三方库。

常见的支持 ICE 的库有 Libjingle,Libnice。

  • Libjingle 集成在 WebRTC 里,不方便独立使用。

  • Libnice,常见的 WebRTC 服务器,例如 janus,licode 都是使用 libnice 进行 P2P 通信,具体可访问 Libnice 官网了解。

当然还有 STUNMAN、coturn 等,coturn 目前有在 GitHub 上有 7.9k star,应该是最火的库了。

移动网络的穿透

但凡涉及到移动网络,穿透就是个麻烦事。麻烦在于移动网络的公网 IP 可能会变,换了一个基站或者切换一次网络连接,公网 IP 就变了。

网友 tryme 测试的 STUN 服务器穿透结果如下:

  1. 同一个局域网的两台移动设备可以连接进行通信
  2. 移动 4G 与 WiFi,移动 4G 与联通 4G,移动 4G 与电信 4G 都是可以连通进行通信
  3. 联通 4G 与 WiFi,电信 4G 与 WiFi,WiFi 与 WiFi 偶尔可以通信,但是大多数是不可以连通的

经过各种折腾发现原因如下:

  1. 运营商的 NAT 给电信 4G 或者联通 4G 分配的内网外网 IP 映射每次切换数据网络是一直变化的
  2. 电信 4G 与联通 4G 分配是多 IP,而移动的 4G 分配的公网 IP 是不变的 (在 P2P 连接使用中),电信、联通 4G 分配的公网 IP 一直是变化的且前后端 debug 到的 ip 是不同的
  3. 当两台移动设备尝试建立连接时,如果通过 STUN 服务器无法穿透,STUN 服务器的日志中显示双方在不断的尝试穿透

NAT 穿透技术也不是完美的,不是任何一个 NAT 都可以穿透,当终端隐藏在非常复杂的路由背后,穿透技术无能为力。因为 NAT 本身还不是一个标准,每个通信设备厂家的实现会有差别。尤其是跨不同网络环境的情况,穿透失败是经常的事,那时只能 Fallback 到透传了。

参考资料

SIP 协议

STUN/TURN/ICE 标准协议

  • RFC 5245 – Interactive Connectivity Establishment (ICE): A Protocol for Network Address Translator (NAT) Traversal for Offer/Answer Protocols
  • RFC 5389 – Session Traversal Utilities for NAT (STUN)
  • RFC 5766 – Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT (STUN)
  • RFC 5768 – Indicating Support for Interactive Connectivity Establishment (ICE) in the Session Initiation Protocol (SIP)
  • RFC 5928 – Traversal Using Relays around NAT (TURN) Resolution Mechanism
  • RFC 6062 – Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations
  • RFC 6156 – Traversal Using Relays around NAT (TURN) Extension for IPv6
  • RFC 6336 – IANA Registry for Interactive Connectivity Establishment (ICE) Options
  • RFC 6544 – TCP Candidates with Interactive Connectivity Establishment

Kamailio 资料

FreeSWITCH 资料

NAT 穿透

其他资料