SIP 是一个对等的协议,类似 P2P。它可以在不需要服务器的情况下进行通信,只要通信双方都彼此指导对方的地址(或者只有一方知道另一方的地址)即可,这种情况称为点对点通信。详细标准可查看RFC3261
SIP 协议采用 Client/Server 模型,每一个请求(request),Server 从接受到请求到处理完毕,要回复多个临时响应,和有且仅有一个终结响应(response)。
概念
Transaction 请求和所有的响应构成一个事务,一个完整的呼叫过程包括多个事件。
UA 用户代理,是发起或接受呼叫的逻辑实体
UAC 用户代理客户端,用于发起请求
UAS 用户代理服务器,用于接受请求
UAC 和 UAS 的划分是针对一个事务的,在一个呼叫的多个事务中,UAC 和 UAS 的角色是可以互相转换的
B2BUA 是一个 SIP 中逻辑上的网络组件,用于操作不同会话的端点,它将 channel 划分为两路通话,在不同会话的端点直接通信。例如,当建立一通呼叫时,B2BUA 作为一个 UAS 接受所有用户请求,处理后以 UAC 角色转发至目标端。
SIP URI
假设 Bob 在服务器 192.168.1.100,Alice 在 192.168.1.200 上,FreeSwitch 在 192.168.1.9 上,Alice 注册到 FreeSwitch 上,Bob 呼叫 Alice 时,使用 Alice 的服务器地(又称逻辑地址)(Bob 只知道服务器地址),即 sip:Alice@192.168.1.9,FreeSwitch 接受到请求后,查找本地数据库,发现 Alice 的实际地址(Contact 地址,又叫联系地址,亦称物理地址)是 sip:Alice@192.168.1.100,便可以建立呼叫。Bob 作为主叫方,它已经知道服务器地址,可以直接发送 INVITE 请求,是不需要注册的,而 Alice 作为被叫的一方,为了让服务器能找到它,它必须事先通过 REGISTER 消息注册到服务器上。
媒体
音频编码
从模拟信号变成数字信号的过程称模数转换(Analog Digital Convert ,AD)。AD 转换要经过采样、量化、编码三个过程。编码就是按照一定的规则将采样所得的信号用一组二进制或者其他进制的数来表示。经过编码后的数据便于在网络上传输,到达对端后,在通过解码过程变成原始信号,进而经过数模转换(DA)在恢复为模拟量,即转换为人们能够感知的信号。一般来说,编码与解码都是成对出现的,称为编解码(codec),一般简称编码。
音频编码最基本的两个技术参数就是采样率和打包周期。
- 采样率 采样频率越高,声音就越清晰,保留的细节就越多。对于普通的人声通话来说,8000Hz 就够了。
- 打包周期 打包周期和传输有关,打包周期越短,延迟越小,相对而言传输开销就会越多。大部分编码都支持多种打包周期,常见的打包周期为 20ms。
FreeSwitch 支持的语音编码,
1 | show codec |
更多编码可参考wiki
当我们重新加载模块时,可以看到相关音频编码的加载过程
1 | reload mod_g723_1 |
媒体协商
不同的 SIP 终端有不同的特性,支持不同的语音编码。所以不同的 SIP 终端进行通信时需要先与支持的编码进行“协商”,以便双方互相能够理解对方发来的媒体流中的数据。 我们来看一个最简单的编码协商过程。在 FreeSwitch 中我们将日志级别调整为
1 | 或者按快捷键F8 |
在 log 中我们可以看到如下的协商过程
1 | Audio Codec Compare [opus:113:48000:20:0:2]/[G722:9:8000:20:64000:1] |
SIP 采用 Off/Anwser(请求/应答)机制来协商。请求发起的一方提供(Offer)自己支持的媒体编码列表,被请求的一方比较自己支持的媒体列表最终选择一种(或几种)编码以应答(Anwser)方式通知请求者,然后他们就可以使用兼容的编码进行通信了。上述 Log,最终可以看到,他也是将客户端与服务器的编码进行逐一比较,最后Set Codec sofia/internal/1001@10.211.55.6 G722/8000 20 ms 160 samples 64000 bits 1 channels
,表示本次协商成功并把该 Channel 的编码设置为 G722 编码
我们通过拨打一通电话,抓取 tcpdump 报文,详细查看一下相关的 SIP 信令
我们查看一个 INVITE 请求(删减部分信息)
1 | INVITE sip:5000@10.211.55.6 SIP/2.0 |
当 Freeswitch 收到请求后,即启动协商过程。根据前面的 Log 所以,服务器提供 OPUS,G722,PCMU,PCMA 等。因此当比较到 G7222 时协商成功,FreeSwitch 返回如下 SIP 消息
1 | SIP/2.0 200 OK |
协商过程完毕,双方互相知道了对方的 IP 地址和端口好,就可以互发音频 RTP 包了。
FreeSwitch 协商策略
- generous 优先采用客户端的编码
- greedy 优先选择服务端即 FreeSwitch 的编码
- scrooge 优先选择服务端即 FreeSwitch 的编码并强制使用自己的采样率
FreeSwitch 转码
FreeSwitch 作为一个 B2BUA,因而在桥接两条腿时,如果两条腿分别使用不同的编码,则需要经过一个转码过程分别转成对方需要的编码,当需要转码时,FreeSwitch 回将收到的音频数据转换成一种中间格式,称为 L16,即线性 16 位的编码,这种格式可以和其他各种编码进行转换。
FreeSwitch 其他
- 透传 指在不经过转码的情况下,将从一方收到的媒体流原样转给另一方
- 媒体绕过 媒体绕过技术,即真正的媒体流使用点到点传输,根本不经过 freeswitch
- 媒体代理 即不管 freeswitch 是否支持对该种编码转码,他都对 rtp 数据在不进行任何处理的情况下发送给另一方,与透传的区别是:他只改变 sdp 中的“c=”部分。
- 媒体 Bug,用作监听,检测等
- 使用 uuid_debug_media 拍错,查看日志
Audio Codec Compare
相关行
SDP
SIP 负责建立和释放会话,一般来说,会话会包含相关的媒体,如视频和音频。媒体数据是由 SDP(Session Description Protocol,会话描述协议)描述的,SDP 一般不单独使用,它与 SIP 配合使用时会放到 SIP 协议的 Boby(正文)中。会话建立时,需要媒体协商,双方才能确定对方的媒体能力以交互媒体数据,比如确认支持的数据格式。 SDP 的特点
- 是一个结构化的文本协议
- 表述 session 的媒体,协议,编码译码格式等
- 通常的 SDP 消息格式为
type=parameter1 parameter2 ... parameterN
SDP 参数
1 | *表示可选参数 |
支持的 codec 类型,在 SDP 中使用数字表示,常见的如下
数字 | 编码 |
---|---|
8 | PCMA |
示例
1 | v = 0 |
一个标准的呼叫过程
1 | Alice Bob |
其 sip 报文大致如下:
F1 INVITE Alice -> Bob
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:alice@client.atlanta.example.com;transport=tcp>
Content-Type: application/sdp
Content-Length: 151
v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000F2 180 Ringing Bob -> Alice
1
2
3
4
5
6
7
8
9SIP/2.0 180 Ringing
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com;transport=tcp>
Content-Length: 0F3 200 OK Bob -> Alice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18SIP/2.0 200 OK
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact: <sip:bob@client.biloxi.example.com;transport=tcp>
Content-Type: application/sdp
Content-Length: 147
v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000F4 ACK Alice -> Bob
1
2
3
4
5
6
7
8ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bd5
Max-Forwards: 70
From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 ACK
Content-Length: 0省略通话中的 RTP 媒体信息,通话过程中一般不再有 SIP 消息交互,所有的语音数据都是在 RTF 中传送。
F5 BYE Bob -> Alice
1
2
3
4
5
6
7
8BYE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
Max-Forwards: 70
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0F6 200 OK Alice -> Bob
1
2
3
4
5
6
7
8SIP/2.0 200 OK
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0
基本方法
REGISTER
通常的注册流程是 Bob 向 FreeSwitch 发起注册(REGISTER)请求,FreeSwitch 返回 Challenge,Bob 将自己的用户名与密码和 Challenge 进行计算,并将计算结果加密附加到下一次 REGISTER 请求上,重新发起注册。FreeSwitch 收到后对本地数据库中保存的 Alice 信息使用同样的算法进行计算和加密,与 Bob 发送的计算结果进行比对,如果相同,则认证通过。其交互流程如下
1 | Bob FreeSwitch |
使用 tcpdump 抓取报文,可以观察到具体细节
1 | 16:15:09.171545 IP 10.211.55.2.56752 > 10.211.55.6.5060: SIP: REGISTER sip:10.211.55.6 SIP/2.0 |
当密码输错的时候,会返回 403
1 | ................ |
INVITE
初始化一个会话,可以理解为发起一个呼叫
ACK
对 INVITE 消息的最终响应
CANCEL
取消一个等待处理或正在处理的请求
BYE
终止一个会话
OPTIONS
可以用来查询服务器支持的信令,codes 等,也可以用作 ping 测试。
1 | OPTIONS sip:bob@gauss.com SIP/2.0 |
1 | SIP/2.0 200 OK |
- Allow 表示服务端可处理的 sip 信令
- Accept 表示服务端可处理 SDP
- SDP 消息中的 m 行,数字 0 用来防止媒体流初始化。后面的
0 8
,表示支持的 audio 的 codecs。下面的两个 a 行,是对其具体描述
SIP 报文头域
所有 SIP 消息都必须包含以下前 6 个头域
- Call-ID 用于区分不同会话的唯一标志
- CSeq 序列号,用于在同一会话中区分事务
- From 说明请求来源
- To 说明请求接受方
- Max-Forwars 限制跳跃点数和最大转发次数
- Via 描述请求消息经过的路径
request 和 response 报文的 From 和 To 是完全一致的,尽管他们的方向是相反的。From 和 To 的值是根据 request 来定义的。 Via 中的 branch 标记腿的 id 当有使用 proxy 代理时,请求转发时用的还是同一个 branch,From 和 To 中的 tag 可以作为 id 标记是否为原始请求。
Via 的一个示例
更多的请参考SIP 参数
状态码
与 HTTP 响应类似,状态码由 3 位数字组成
- 1xx 临时状态,表明呼叫进展的情况
- 2xx 表明请求一杯成功收到,理解和接收
- 3xx 重定向,表明 SIP 请求需要转向到另一个 UAS 处理
- 4xx 表明请求失败,这种失败一般由客户端或网络引起的,如密码错误,空号,客户端应该重新修改请求,然后重发
- 5xx 服务器内部错误
- 6xx 全局性错误,如 600 Busy Everywhere
状态码后面跟着一个原因短语(如 200 OK 中的 OK),它是对前面的状态码的一个简单解释
常用状态码 | 状态码| 说明| | :---- | :---- | |180|振铃| |488|不兼容的媒体类型|
具体状态码见SIP 状态码 WIKI
FreeSwitch 中的 SIP 模块
基本概念
- Sofia-SIP FreeSwitch 的 SIP 功能是在 mod_sofia 模块中实现的。FreeSwitch 并没有自己开发新的 SIP 协议栈,而是使用了比较成熟的开源 SIP 协议栈 Sofia-SIP。
- Endpoint 在 FreeSwitch 中,实现一些互联网协议接口的模块称为 Endpoint。FreeSwitch 支持很多类型的 Endpoint,如 SIP,H232 等。这些不同的 Endpoint 主要是使用不同的控制协议跟其他的 Endpoint 通话。
- mod_sofia mod_sofia 实现了 SIP 中的注册服务器、重定向服务器、媒体服务器,呈现服务器、SBC 等各种功能。它的定位是一个 B2BUA。
- SIP Profile 在 mod_sofia 中,SIP Profile 相当于一个 SIPUA,通过各种不同的参数可以配置一个 UA 的行为。一个系统可以有多个 SIP Profile,每个 SIP Profile 都可以监听不同的 IP 地址和端口。
- Gateway 一个 SIP Profile 中有多个 Gateway(网关),它主要用于定义一个远程的 SIP 服务器,使 Freeswitch 可以与其他服务器通信。
- 本地 SIP 用户 FreeSwitch 可以作为注册服务器,这时候其他 SIP 客户端就可以向它注册。FreesWitch 将通过用户目录中(conf/Directory)中的配置信息对注册用户进行鉴权。这些 SIP 客户端锁代表的用户就称为本地 SIP 用户,简称本地用户
- 来电去话,中继来电,中继去话
配置文件
Sofia 的配置文件在autoload_configs/sofia.conf.xml
中。Sofia 支持多个 Profile,而每一个 Profile 相当于一个 SIP UA,在启动后悔监听一个“IP:PORT”对。FreeSwitch 默认的配置带了三个 Profile(也就是三个 UA)。我们不讨论 IPv6,仅讨论 internal 和 external(分别在 internal.xml 和 external.xml 中定义的,分别运行在 5060,5080 端口上)
Profile 的几个重要参数,节选 internal.xml 部分配置
1 | <profile name="internal"> |
external.xml 的配置与 internal.xml 的配置大部分相同,最大的不同是auth-calls
参数,internal.xml 默认为 true,而 external.xml 默认为 false。也就是说,客户端发往 FreeSwitch 的 5060 端口的 SIP 消息需要鉴权(一般只对 REGISTER 和 INVITE 消息进行鉴权),而发往 5080 端口的消息不需要鉴权。我们一般把本地用户都注册到 5060 上,所以它们打电话时要经过鉴权,保证只有授权用户(本地用户目录中配置的)才能注册和拨打电话。而 5080 则不同,任何人均可以向该端口发送 SIP INVITE 请求。
Gateway(网关)
在 external.xml 中我们可以看到它使用预处理指令将 external 目录下的所有 XML 配置文件都装入到 external 的 Profile 文件的 gateways 标签中:
1 | <profile name="external"> |
节选部分 Gateway 配置
1 | <gateway name="唯一网关名称"> |
呼叫是如何工作的
我们假设用的是默认配置,并从 1000 呼叫 1001。
- 1000 的 SIP 话机作为 UAC 会发送 INVITE 请求到 FreeSwitch 的 5060 端口,也就是达到 mod_sofia 的 internal 这个 Profile 所配置的 UAS,该 UAS 收到正确的 INVITE 请求后会返回 100 响应码,表示我收到你的请求了。该 UAS 对所有收到的 INVITE 都要进行鉴权(因为 auth-calls=true)。它会检测 ACL(访问控制列表,一般用于 IP 鉴权)。默认的 ACL 检查是不通过的因此就会走到密码鉴权(HTTP 协议中的 Digest Auth)阶段。一般是 UAS 回复 401。
UAC 重新发送带鉴权信息的 INVITE,UAS 收到后,便将鉴权信息提交到上层的 FreeSwitch 代码,FreeSwitch 就到会 Directory(用户目录)查找相应的用户。此处,它会找到
conf/directory/default/1000.xml
文件中配置的用户信息,并根据其中配置的密码进行鉴权。如果鉴权不通过,返回 403 Forbidden 等错误信息,通话结束。如果鉴权通过,FreeSwitch 就取到了用户的信息,比较重要的是 user_context,在我们的例子中它的值为 default。接下来电话进入路由(routing)阶段,开始查找 Dialplan。用于该用户的 Context 是 default,因此路由就从 default 这个 Dialplan 查起(即conf/dialplan/default.xml
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<include>
<user id="1001">
<params>
<param name="password" value="$${default_password}"/>
<param name="vm-password" value="1001"/>
</params>
<variables>
<variable name="toll_allow" value="domestic,international,local"/>
<variable name="accountcode" value="1001"/>
<variable name="user_context" value="default"/>
<variable name="effective_caller_id_name" value="Extension 1001"/>
<variable name="effective_caller_id_number" value="1001"/>
<variable name="outbound_caller_id_name" value="$${outbound_caller_name}"/>
<variable name="outbound_caller_id_number" value="$${outbound_caller_id}"/>
<variable name="callgroup" value="techsupport"/>
</variables>
</user>
</include>查找 Dialplan,找到 1001 这个用户,并执行 bridge user/1001,在这里 user/1001 称为呼叫字符串,它会再次查找 Directory,找到
conf/directory/default/1001.xml
里配置的参数,由于 1001 是被叫,因此他会进一步查找直到查到 1001 实际注册的位置,由于所有用户的规则都是一样,因此该参数被放到conf/direcotry/default.xml
中,在该文件中可以看到如下配置1
2
3
4
5
6
7<domain name="$${domain}">
<params>
<param name="dial-string" value="{^^:sip_invite_domain=${dialed_domain}:presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(*/${dialed_user}@${dialed_domain})},${verto_contact(${dialed_user}@${dialed_domain})}"/>
<!-- These are required for Verto to function properly -->
<param name="jsonrpc-allowed-methods" value="verto"/>
<!-- <param name="jsonrpc-allowed-event-channels" value="demo,conference,presence"/> -->
</params>其中,最关键的是 sofia_contact 这个 API 调用,它会查找数据库,找到 1001 实际注册的 Contact 地址,并返回真正的呼叫字符串。
1
2sofia_contact 1001
sofia/internal/sip:46598071@10.211.55.2:51032找到呼叫字符串后,FreeSwitch 又启动另外一个会话作为一个 UAC 给 1001 发送 INVITE 请求,如果 1001 摘机,则 1001 向 FreeSwitch 回送 200 OK 消息,FreeSwitch 再向 100 返回 200OK,通话开始。
FreeSwitch 是一个 B2BUA,上面的过程建立了一通会话,其中有两个 Channel。我们可以跟踪 SIP 消息试一下sofia profile internal siptrace on/off
在 FreeSwitch 的默认配置中,external 对应的 Profile 是不鉴权的,凡是送到 5080 端口的 INVITE 都不需要鉴权。
- SIPUA 直接把 INVITE 送到任意端口,一般用于中继方式对接
- FreeSwitch 作为一个客户端,若要添加一个网关,则该网关会被放到
sip_profiles/external/
的文件中,它就会被包含到sip_profiles/external.xml
中。它向其他服务器注册时,其中的 Contact 地址就是 IP:5080,如果有来话,对方的服务器就会把 INVITE 送到它的 5080 端口