第四章 SIP
在继续学习 FreeSWITCH 之前我们有必要来学习一下 SIP 协议,因为它是 FreeSWITCH 的核心。但即使如此,讲清楚 SIP 必然需要很大篇幅,本书是关于 FreeSWITCH 的,而重点不是 SIP。因此,我将仅就理解 FreeSWITCH 必需的一些概念加以通俗的解释,更严肃一些的请参阅其它资料或 RFC(Request For Comments)。
SIP 的概念和相关元素
会话初始协议(Session Initiation Protocol)是一个控制发起、修改和终结交互式多媒体会话的信令协议。它是由 IETF(Internet Engineering Task Force,Internet工程任务组)在 RFC 2543 中定义的。最早发布于 1999 年 3 月,后来在 2002 年 6 月又发布了一个新的标准 RFC 2361。
SIP 是一个基于文本的协议,在这一点上与 HTTP 和 SMTP 相似。我们来对比一个简单的 SIP 请求与 HTTP 请求:
GET /index.html HTTP/1.1
INVITE sip:seven@freeswitch.org.cn SIP/2.0
请求由三部分组成。在 HTTP 中, GET 指明一个获取资源(文件)的动作,而 /index.html 则是资源的地址,最后是协议版本号。而在 SIP 中,INVITE 表示发起一次请求,seven@freeswitch.org.cn 为请求的地址,称为 SIP URI,最后也是版本号。其中,SIP URI很类似一个电子邮件,其格式为“协议:名称@主机”。与 HTTP 和 HTTPS 相对应,有 SIP 和 SIPS,后者是加密的;名称可以是一串数字的电话号码,也可以是字母表示的名称;而主机可以是一个域名,也可以是一个IP地址。
SIP 是一个对等的协议,类似 P2P。不像传统电话那样必须有一个中心的交换机,它可以在不需要服务器的情况下进行通信,只要通信双方都彼此知道对方地址(或者,只有一方知道另一方地址),如下图,bob 给 alice 发送一个 INVITE 请求,说“Hi, 一起吃饭吧...”,alice 说"好的,OK",电话就通了。
在 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 都会实现上述两种功能。
设想 bob 和 alice 是经人介绍认识的,而他们还不熟悉,bob 想请 alice 吃饭就需要一个中间人(M)传话,而这个中间人就叫代理服务器(Proxy Server)。还有另一种中间人叫做重定向服务器(Redirect Server),它类似于这样的方式工作──中间人 M 告诉 bob,我也不知道 alice 在哪里,但我老婆知道,要不然我告诉你我老婆的电话,你直接问她吧,我老婆叫 W。这样,M 就成了一个重定向服务器,而他老婆 W 则是真正的代理服务器。这两种服务器都是 UAS,它们主要是提供一对欲通话的 UA 之间的路由选择功能。具有这种功能的设备通常称为边界会话控制器(SBC,Session Border Controller)。
还有一种 UAS 叫做注册服务器。试想这样一种情况,alice 还是个学生,没有自己的手机,但它又希望 bob 能随时找到她,于是当她在学校时就告诉中间人 M 说她在学校,如果有事打她可以打宿舍的电话;而当她回家时也通知 M 说有事打家里电话。只要 alice 换一个新的位置,它就要向 M 重新“注册”新位置的电话,以让 M 能随时找到她,这时候 M 就是一个注册服务器。
最后一种叫做背靠背用户代理(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 只能“背对背”的工作。
从上图可以看出,四个人其实全是 UA。从上面故事可以看出,虽然 FreeSWITCH 是 B2BUA,但也可以经过特殊的配置,实现一些代理服务器和重定向服务器的功能,甚至也可以从中间劈开,两边分别作为一个普通的 UA 来工作。这没有什么奇怪的,在 SIP 世界中,所有 UA 都是平等的。具体到实物,则 M 和 W 就组成了实现软交换功能的交换机,它们对外说的语言是 SIP,而在内部,它们则使用自己家的语言沟通。bob 和 alice 就分别成了我们常见的软电话,或者硬件的 SIP 电话。
SIP 注册
不像普通的固定电话网中,电话的地址都是固定的。因特网是开放的,alice 的 UA 可能在家也可能在学校,或者,在世界是任何角落,只要能上网,它就能与世界通信。为了让我们的 FreeSWITCH 服务器能找到它,它必须向服务器进行注册。通常的注册流程是:
Alice FreeSWITCH
| |
| REGISTER |
|------------------------------->|
| SIP/2.0 401 Unauthorized |
|<-------------------------------|
| REGISTER |
|------------------------------->|
| SIP/2.0 200 OK |
| |
我们用真正的注册流程进行说明。下面的 SIP 消息是在真正的 FreeSWITCH 中 trace 出来的。其中 FreeSWITCH 服务器的 IP 地址是 192.168.4.4,使用默认的端口号 5060,在这里,我们使用的是 UDP 协议。 alice 使用的 UAC 是 Zoiper,端口号是 5090(在我写作时它与 FreeSWITCH 在同一台机器上,所以不能再使用端口 5060)。其中每个消息短横线之间的内容都是 FreeSWITCH 中输出的调试信息,不是 SIP 的一部分。
------------------------------------------------------------------------
recv 584 bytes from udp/[192.168.4.4]:5090 at 12:30:57.916812:
------------------------------------------------------------------------
REGISTER sip:192.168.4.4;transport=UDP SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-d9ed3bbae47e568b-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:alice@192.168.4.4:5090;rinstance=d42207a765c0626b;transport=UDP>
To: <sip:alice@192.168.4.4;transport=UDP>
From: <sip:alice@192.168.4.4;transport=UDP>;tag=9c709222
Call-ID: NmFjNzA3MWY1MDI3NGViMjY1N2QwZDlmZWQ5ZGY2OGE.
CSeq: 1 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE
User-Agent: Zoiper rev.5415
Allow-Events: presence
Content-Length: 0
recv 表明 FreeSWITCH 收到来自 alice 的消息。我们前面已经说进,SIP 是纯文本的协议,类似 HTTP,所以很容易阅读。
- 第一行的 REGISTER 表示是一条注册消息。
- Via 是 SIP 的消息路由,如果 SIP 经过好多代理服务器转发,则会有多条 Via 记录。
- Max-forwards 指出消息最多可以经过多少次转发,主要是为了防止产生死循环。
- Contact 是 alice 家的地址,本例中,FreeSWITCH 应该能在 192.168.4.4 这台机器上的 5090 端口找到她。
- To 和 From 先不管。
- Call-ID 是本次 SIP 会话(Session)的标志。
- CSeq 是一个序号,由于 UDP 是不可靠的协议,在不可靠的网络上可能丢包,所以有些包需要重发,该序号则可以防止重发引起的消息重复。
- Expires 是说明本次注册的有效期,单位是秒。在本例中,alice 应该在一小时内再次向 FreeSWITCH 注册,防止 FreeSWITCH 忘掉她。实际上,大部分 UA 的实现都会在几十秒内就重新发一次注册请求,这在 NAT 的网络中有助于保持连接。
- Allow 是说明 alice 的 UA 所能支持的功能,某些 UA 功能丰富,而某些 UA 仅有有限的功能。
- User-Agent 是 UA 的型号。
- Allow-Events 则是说明她允许哪些事件通知。
- Content-Length 是消息体(Body)的长度,在这里,只有消息头(Header),没有消息体,因此长度为 0 。
. ------------------------------------------------------------------------ send 664 bytes to udp/[192.168.4.4]:5090 at 12:30:57.919364: ------------------------------------------------------------------------ SIP/2.0 401 Unauthorized Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-d9ed3bbae47e568b-1---d8754z-;rport=5090 From: <sip:alice@192.168.4.4;transport=UDP>;tag=9c709222 To: <sip:alice@192.168.4.4;transport=UDP>;tag=QFXyg6gcByvUH Call-ID: NmFjNzA3MWY1MDI3NGViMjY1N2QwZDlmZWQ5ZGY2OGE. CSeq: 1 REGISTER User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE Supported: timer, precondition, path, replaces WWW-Authenticate: Digest realm="192.168.4.4", nonce="62fb812c-71d2-4a36-93d6-e0008e6a63ee", algorithm=MD5, qop="auth" Content-Length: 0
FreeSWITCH 需要验证 alice 的身分才允许她注册。在 SIP 中,没有发明新的认证方式,而是使用已有的 HTTP 摘要(Digest)方式来认证。401 消息表示未认证,它是 FreeSWITCH 对 alice 的响应。同时,它在本端生成一个认证摘要(WWW-Authenticate),一齐发送给 alice。
------------------------------------------------------------------------
recv 846 bytes from udp/[192.168.4.4]:5090 at 12:30:57.921011:
------------------------------------------------------------------------
REGISTER sip:192.168.4.4;transport=UDP SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-dae1693be9f8c10d-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:alice@192.168.4.4:5090;rinstance=d42207a765c0626b;transport=UDP>
To: <sip:alice@192.168.4.4;transport=UDP>
From: <sip:alice@192.168.4.4;transport=UDP>;tag=9c709222
Call-ID: NmFjNzA3MWY1MDI3NGViMjY1N2QwZDlmZWQ5ZGY2OGE.
CSeq: 2 REGISTER
Expires: 3600
Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE
User-Agent: Zoiper rev.5415
Authorization: Digest username="alice",realm="192.168.4.4",
nonce="62fb812c-71d2-4a36-93d6-e0008e6a63ee",
uri="sip:192.168.4.4;transport=UDP",response="32b5ddaea8647a3becd25cb84346b1c3",
cnonce="b4c6ac7e57fc76b85df9440994e2ede8",nc=00000001,qop=auth,algorithm=MD5
Allow-Events: presence
Content-Length: 0
alice 收到带有摘要的 401 后,后新发起注册请求,这一次,加上了根据收到的摘要和它自己的密码生成的认证信息(Authorization)。并且,你可以看到,CSeq 序号变成了 2。
------------------------------------------------------------------------
send 665 bytes to udp/[192.168.4.4]:5090 at 12:30:57.936940:
------------------------------------------------------------------------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-dae1693be9f8c10d-1---d8754z-;rport=5090
From: <sip:alice@192.168.4.4;transport=UDP>;tag=9c709222
To: <sip:alice@192.168.4.4;transport=UDP>;tag=rrpQj11F86jeD
Call-ID: NmFjNzA3MWY1MDI3NGViMjY1N2QwZDlmZWQ5ZGY2OGE.
CSeq: 2 REGISTER
Contact: <sip:alice@192.168.4.4:5090;rinstance=d42207a765c0626b;transport=UDP>;expires=3600
Date: Tue, 27 Apr 2010 12:30:57 GMT
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Content-Length: 0
FreeSWITCH 收到带有认证的注册消息后,核实 alice 身份,认证通过,回应 200 OK。 如果失败,则回应 403 Forbidden 或其它失败消息,如下。
------------------------------------------------------------------------
send 542 bytes to udp/[192.168.4.4]:5090 at 13:22:49.195554:
------------------------------------------------------------------------
SIP/2.0 403 Forbidden
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-d447f43b66912a1b-1---d8754z-;rport=5090
From: <sip:alice@192.168.4.4;transport=UDP>;tag=c097e17f
To: <sip:alice@192.168.4.4;transport=UDP>;tag=yeecX364pvryj
Call-ID: ZjkxMGJmMjE4Y2ZiNjU5MzM5NDZkMTE5NzMzMzM0Mjc.
CSeq: 2 REGISTER
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Content-Length: 0
你可以看到,alice 的密码是不会直接在 SIP 中传送的,因而一定程序上保证了安全(当然还是会有中间人,重放之类的攻击,我们留到后面讨论)。
SIP 呼叫流程
UA 间直接呼叫
上面我们说过,SIP 的 UA 是平等的,如果一方知道另一方的地址,就可以通信。我们先来做一个实验。在笔者的机器上,我启动了两个软电话(UA), 一个是 bob 的 X-Lite(左),另一个是 alice 是 Zoiper。它们的 IP 地址都是 192.168.4.4,而端口号分别是 26000 和 5090,当 bob 呼叫 alice 时,它只需直接呼叫 alice 的 SIP 地址:sip:alice@192.168.4.4:5090。如图,alice 的电话正在振铃:
详细的呼叫流程图为:
bob alice
| |
| INVITE |
|-------------------->|
| 100 Trying |
|<--------------------|
| 180 Ringing |
|<--------------------|
| 200 OK |
|<--------------------|
| ACK |
|-------------------->|
| |
|<---RTP------------->|
|<---RTP------------->|
|<---RTP------------->|
| ... |
| |
| BYE |
|<--------------------|
| 200 OK |
|-------------------->|
| |
首先 bob 向 alice 发送 INVITE 请求建立 SIP 连接,alice 的 UA 回 100 Trying 说我收到你的请求了,先等会,接着 alice 的电话开始振铃,并给对回消息 180 Ringing 说我这边已经振铃了,alice 一会就过来接电话,bob 的 UA 收到该消息后可以播放回铃音。接着 alice 接了电话,她发送 200 OK 消息给 bob,该消息是对 INVITE 消息的最终响应,而先前的 100 和 180 消息都是临时状态,只是表明呼叫进展的情况。 bob 收到 200 后向 alice 回 ACK 证实消息。 INVITE - 200 - ACK 完成三次握手,它们合在一起称作一个对话(Dialogue)。这时候 bob 已经在跟 alice 能通话了,他们通话的内容(语音数据)是在SIP之外的 RTP 包中传递的,我们后面再详细讨论。
最后,alice 挂断电话,向 bob 送 BYE 消息,bob 收到 BYE 后回送 200 OK,通话完毕。其中 BYE 和 200 OK 也是一个对话,而上面的所有消息,称作一个会话(Session)。
反过来也一样,alice 可以直接呼叫 bob 的地址: sip:bob@192.168.4.4:26000。
上面描述了一个最简单的 SIP 呼叫流程。实际上,SIP 还有其它一些消息,它们大致可分为请求和响应两类。请求由 UAC 发出,到达 UAS 后, UAS 回送响应消息。某些响应消息需要证实(ACK),以完成三次握手。其中请求消息包括 INVITE、ACK、OPTIOS、BYE、CANCEL、REGISTER 以及一些扩展 re-INVITE、PRACK、SUBSCRIBE、NOTIFY、UPDATE、MESSAGE、REFER等。而响应消息则都包含一个状态码。跟 HTTP 响应类似,状态码有三位数字组成。其中,1xx 组的响应为临时状态,表明呼叫进展的情况;2xx 表明请求已成功处理;3xx 表明 SIP 请求需要转向到另一个 UAS 处理;4xx 表明请求失败,这种失败一般是由客户端或网络引起的,如密码错误等;5xx 为服务器内部错误;6xx 为更严重的错误。
通过 B2BUA 呼叫
在真实世界中,bob 和 alice 肯定要经常改变位置,那么它们的 SIP 地址也会相应改变,并且,如果他们之中有一个或两个处于 NAT 的网络中时,直接通信就更困难了。所以,他们通常会借助于一个服务器来相互通信。通过注册到服务器上,他们都可以获得一个服务器上的 SIP 地址。注册服务器的地址一般是不变的,因此他们的 SIP 地址就不会发生变化,因而,他们总是能够进行通信。
我们让他们两个都注册到 FreeSWITCH 上。上面已经说过,FreeSWITCH 监听的端口是 SIP 默认的端口 5060。bob 和 alice 注册后,他们分别获得了一个服务器的地址(SIP URI):sip:bob@192.168.4.4 和 sip:alice@192.168.4.4(默认的端口号 5060 可以省略)。
下面是 bob 呼叫 alice 的流程。需要指出,如果 bob 只是发起呼叫而不接收呼叫,他并不需要向 FreeSWITCH 注册(有些软交换服务器需要先注册才能发起呼叫,但 SIP 是不强制这么做的)。
------------------------------------------------------------------------
recv 1118 bytes from udp/[192.168.4.4]:26000 at 13:31:39.938891:
------------------------------------------------------------------------
INVITE sip:alice@192.168.4.4 SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-56adad736231f024-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:bob@192.168.4.4:26000>
To: "alice"<sip:alice@192.168.4.4>
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 1 INVITE
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
Content-Type: application/sdp
User-Agent: X-Lite release 1014k stamp 47051
Content-Length: 594
上面的消息中省略了 SDP 的内容,我们将留到以后再探讨。bob 的 UAC 通过 INVITE 消息向 FreeSWITCH 发起请求。bob 的 UAC 用的是 X-Lite(User-Agent),它运行在端口 26000 上(实际上,它默认在端口也是 5060,但由于在我的实验环境下它也是跟 FreeSWITCH 运行在一台机器上,已被占用,因此它需要选择另一个端口)。其中,From 为主叫用户的地址,To 为被叫用户的地址。此时 FreeSWITCH 作为一个 UAS 接受请求并进行响应。它得知 bob 要呼叫 alice,需要在自己的数据库中查找 alice 是否已在服务器上注册,好知道应该怎么找到 alice。但在此之前,它先通知 bob 它已经收到了他的请求。
------------------------------------------------------------------------
send 345 bytes to udp/[192.168.4.4]:26000 at 13:31:39.940278:
------------------------------------------------------------------------
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-56adad736231f024-1---d8754z-;rport=26000
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
To: "alice"<sip:alice@192.168.4.4>
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 1 INVITE
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Content-Length: 0
FreeSWITCH 通过 100 Trying 消息告诉 bob “我已经收到你的消息了,别着急,我正在联系 alice 呢...” 该消息称为呼叫进展消息。
------------------------------------------------------------------------
send 826 bytes to udp/[192.168.4.4]:26000 at 13:31:39.943392:
------------------------------------------------------------------------
SIP/2.0 407 Proxy Authentication Required
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-56adad736231f024-1---d8754z-;rport=26000
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
To: "alice" <sip:alice@192.168.4.4>;tag=B4pem31jHgtHS
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 1 INVITE
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Accept: application/sdp
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Allow-Events: talk, presence, dialog, line-seize, call-info, sla,
include-session-description, presence.winfo, message-summary, refer
Proxy-Authenticate: Digest realm="192.168.4.4",
nonce="31c5c3e0-cc6e-46c8-a661-599b0c7f87d8", algorithm=MD5, qop="auth"
Content-Length: 0
但就在此时,FreeSWITCH 发现 bob 并不是授权用户,因而它需要确认 bob 的身份。它通过发送带有 Digest 验证信息的 407 消息来通知 bob(注意,这里与注册流程中的 401 不同)。
------------------------------------------------------------------------
recv 319 bytes from udp/[192.168.4.4]:26000 at 13:31:39.945314:
------------------------------------------------------------------------
ACK sip:alice@192.168.4.4 SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-56adad736231f024-1---d8754z-;rport
To: "alice" <sip:alice@192.168.4.4>;tag=B4pem31jHgtHS
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 1 ACK
Content-Length: 0
bob 回送 ACK 证实消息向 FreeSWITCH 证实已收到认证要求。并重新发送 INVITE,这次,附带了验证信息。
------------------------------------------------------------------------
recv 1376 bytes from udp/[192.168.4.4]:26000 at 13:31:39.945526:
------------------------------------------------------------------------
INVITE sip:alice@192.168.4.4 SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-87d60b47b6627c3a-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:bob@192.168.4.4:26000>
To: "alice"<sip:alice@192.168.4.4>
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 2 INVITE
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO
Content-Type: application/sdp
Proxy-Authorization: Digest username="bob",realm="192.168.4.4",
nonce="31c5c3e0-cc6e-46c8-a661-599b0c7f87d8",
uri="sip:alice@192.168.4.4",response="327887635344405bcd545da06763c466",
cnonce="c164b74f625ff2161bd8d47dba3a0ee2",nc=00000001,qop=auth,
algorithm=MD5
User-Agent: X-Lite release 1014k stamp 47051
Content-Length: 594
这里也省略了 SDP 消息体。
------------------------------------------------------------------------
send 345 bytes to udp/[192.168.4.4]:26000 at 13:31:39.946349:
------------------------------------------------------------------------
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-87d60b47b6627c3a-1---d8754z-;rport=26000
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
To: "alice"<sip:alice@192.168.4.4>
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 2 INVITE
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Content-Length: 0
FreeSWITCH 重新回 100 Trying,告诉 bob 呼叫进展情况。
至此,bob 与 FreeSWITCH 之间的通信已经初步建立,这种通信的通道称作一个信道(channel)。该信道是由 bob 的 UA 和 FreeSWITCH 的一个 UA 构成的,我们称它为 FreeSWITCH 的一条腿,叫做 a-leg。
接下来 FreeSWITCH 要建立另一条腿,称为 b-leg。它通过查打本地数据库,得到了 alice 的位置,接着启动一个 UA(用作 UAC),向 alice 发送 INVITE 消息。如下:
------------------------------------------------------------------------
send 1340 bytes to udp/[192.168.4.4]:5090 at 13:31:40.028988:
------------------------------------------------------------------------
INVITE sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4;rport;branch=z9hG4bKey90QUyHZQXNN
Route: <sip:alice@192.168.4.4:5090>;rinstance=e7d5364c81f2b879;transport=UDP
Max-Forwards: 69
From: "Bob" <sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
To: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 130069214 INVITE
Contact: <sip:mod_sofia@192.168.4.4:5060>
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Allow-Events: talk, presence, dialog, line-seize, call-info, sla,
include-session-description, presence.winfo, message-summary, refer
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 313
X-FS-Support: update_display
Remote-Party-ID: "Bob" <sip:bob@192.168.4.4>;party=calling;screen=yes;privacy=off
你可以看到,该INVITE 的 Call-ID 与前面的不同,说明这是另一个 SIP 会话(Session)。另外,它还多了一个 Remote-Party-ID,它主要是用来支持来电显示。因为,在 alice 的话机上,希望显示 bob 的号码,显示呼叫它的那个 UA(负责 b-leg的那个 UA)没什么意义。与普通的 POTS 电话不同,在 SIP 电话中,不仅能显示电话号码(这里是 bob),还能显示一个可选的名字(“Bob”)。这也说明了 FreeSWITCH 这个 B2BUA 本身是一个整体,它虽然是以一个单独的 UA 呼叫 alice,但还是跟负责 bob 的那个 UA 有联系--就是这种背靠背的串联。
------------------------------------------------------------------------
recv 309 bytes from udp/[192.168.4.4]:5090 at 13:31:40.193634:
------------------------------------------------------------------------
SIP/2.0 100 Trying
Via: SIP/2.0/UDP 192.168.4.4;rport=5060;branch=z9hG4bKey90QUyHZQXNN
To: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>
From: "Bob" <sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 130069214 INVITE
Content-Length: 0
跟上面的流程差不多,alice回的呼叫进展。此时,alice 的 UA 开始振铃。
------------------------------------------------------------------------
recv 431 bytes from udp/[192.168.4.4]:5090 at 13:31:40.193816:
------------------------------------------------------------------------
SIP/2.0 180 Ringing
Via: SIP/2.0/UDP 192.168.4.4;rport=5060;branch=z9hG4bKey90QUyHZQXNN
Contact: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>
To: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>;tag=3813e926
From: "Bob"<sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 130069214 INVITE
User-Agent: Zoiper rev.5415
Content-Length: 0
180也是呼叫进展消息,它说明,我这边已经准备好了,alice 的电话已经响了,她听到了一会就会接听。
send 1125 bytes to udp/[192.168.4.4]:26000 at 13:31:40.270533:
------------------------------------------------------------------------
SIP/2.0 183 Session Progress
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-87d60b47b6627c3a-1---d8754z-;rport=26000
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
To: "alice" <sip:alice@192.168.4.4>;tag=cDg7NyjpeSg4m
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 2 INVITE
Contact: <sip:alice@192.168.4.4:5060;transport=udp>
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Accept: application/sdp
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Allow-Events: talk, presence, dialog, line-seize, call-info, sla,
include-session-description, presence.winfo, message-summary, refer
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 267
Remote-Party-ID: "alice" <sip:alice@192.168.4.4>
FreeSWITCH 在收到 alice 的 180 Ringing 消息后,便告诉 bob 呼叫进展情况,183 与 180 不同的是,它包含 SDP,即接下来它会向 bob 发送 RTP 的媒体流,就是回铃音。
------------------------------------------------------------------------
recv 768 bytes from udp/[192.168.4.4]:5090 at 13:31:43.251980:
------------------------------------------------------------------------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.4.4;rport=5060;branch=z9hG4bKey90QUyHZQXNN
Contact: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>
To: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>;tag=3813e926
From: "Bob"<sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 130069214 INVITE
Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE
Content-Type: application/sdp
User-Agent: Zoiper rev.5415
Content-Length: 226
alice 接听电话以后,其 UA 向 FreeSWITCH 送 200 OK,即应答消息。
------------------------------------------------------------------------
send 436 bytes to udp/[192.168.4.4]:5090 at 13:31:43.256692:
------------------------------------------------------------------------
ACK sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4;rport;branch=z9hG4bKF72SSpFNv0K8g
Max-Forwards: 70
From: "Bob" <sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
To: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>;tag=3813e926
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 130069214 ACK
Contact: <sip:mod_sofia@192.168.4.4:5060>
Content-Length: 0
FreeSWITCH 向 alice 回送证实消息,证实已经知道了。至此,b-leg已经完全建立完毕,多半这时 alice 已经开始说话了:“Hi, bob,你好……”
------------------------------------------------------------------------
send 1135 bytes to udp/[192.168.4.4]:26000 at 13:31:43.293311:
------------------------------------------------------------------------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-87d60b47b6627c3a-1---d8754z-;rport=26000
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
To: "alice" <sip:alice@192.168.4.4>;tag=cDg7NyjpeSg4m
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 2 INVITE
Contact: <sip:alice@192.168.4.4:5060;transport=udp>
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Allow-Events: talk, presence, dialog, line-seize, call-info, sla,
include-session-description, presence.winfo, message-summary, refer
Session-Expires: 120;refresher=uas
Min-SE: 120
Content-Type: application/sdp
Content-Disposition: session
Content-Length: 267
Remote-Party-ID: "alice" <sip:alice@192.168.4.4>
与此同时,它也给 bob 送应答消息,告诉他电话已经接通了,可以跟 alice 说话了。在需要计费的情况下,应该从此时开始对 bob 的电话计费。bob 的 UA 收到该消息后启动麦克风让 bob 讲话。
------------------------------------------------------------------------
recv 697 bytes from udp/[192.168.4.4]:26000 at 13:31:43.413025:
------------------------------------------------------------------------
ACK sip:alice@192.168.4.4:5060;transport=udp SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:26000;branch=z9hG4bK-d8754z-ef53864320037c04-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:bob@192.168.4.4:26000>
To: "alice"<sip:alice@192.168.4.4>;tag=cDg7NyjpeSg4m
From: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 2 ACK
Proxy-Authorization: Digest username="bob",realm="192.168.4.4",
nonce="31c5c3e0-cc6e-46c8-a661-599b0c7f87d8",
uri="sip:alice@192.168.4.4",response="327887635344405bcd545da06763c466",
cnonce="c164b74f625ff2161bd8d47dba3a0ee2",nc=00000001,qop=auth,
algorithm=MD5
User-Agent: X-Lite release 1014k stamp 47051
Content-Length: 0
bob 在收到应答消息后也需要回送证实消息。至此 a-leg 也建立完毕。双方正常通话。
[][][][][][] 此处省略 5000 字 [][][][][]
------------------------------------------------------------------------
recv 484 bytes from udp/[192.168.4.4]:5090 at 13:31:49.949240:
------------------------------------------------------------------------
BYE sip:mod_sofia@192.168.4.4:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-2146ae0ddd113efe-1---d8754z-;rport
Max-Forwards: 70
Contact: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>
To: "Bob"<sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
From: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>;tag=3813e926
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 2 BYE
User-Agent: Zoiper rev.5415
Content-Length: 0
终于聊完了,alice 挂断电话,发送 BYE 消息。
------------------------------------------------------------------------
send 543 bytes to udp/[192.168.4.4]:5090 at 13:31:49.950425:
------------------------------------------------------------------------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.4.4:5090;branch=z9hG4bK-d8754z-2146ae0ddd113efe-1---d8754z-;rport=5090
From: <sip:alice@192.168.4.4:5090;rinstance=e7d5364c81f2b879;transport=UDP>;tag=3813e926
To: "Bob"<sip:bob@192.168.4.4>;tag=Dp9ZQS3SB26pg
Call-ID: 0d74ac35-cca4-122d-81a2-2990e5b2bd3e
CSeq: 2 BYE
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Content-Length: 0
FreeSWITCH 返回 OK,b-leg 释放完毕。
------------------------------------------------------------------------
send 630 bytes to udp/[192.168.4.4]:26000 at 13:31:50.003165:
------------------------------------------------------------------------
BYE sip:bob@192.168.4.4:26000 SIP/2.0
Via: SIP/2.0/UDP 192.168.4.4;rport;branch=z9hG4bKggvjUH0rS99tc
Max-Forwards: 70
From: "alice" <sip:alice@192.168.4.4>;tag=cDg7NyjpeSg4m
To: "Bob" <sip:bob@192.168.4.4>;tag=15c8325a
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 130069219 BYE
Contact: <sip:alice@192.168.4.4:5060;transport=udp>
User-Agent: FreeSWITCH-mod_sofia/1.0.trunk-16981M
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, UPDATE, INFO, REGISTER, REFER,
NOTIFY, PUBLISH, SUBSCRIBE
Supported: timer, precondition, path, replaces
Reason: Q.850;cause=16;text="NORMAL_CLEARING"
Content-Length: 0
接下来 FreeSWITCH 给 bob 发送 BYE,通知要拆线了。出于对 bob 负责,它包含了挂机原因(Hangup Cause),此处 NOMAL_CLEARING 表示正常释放。
------------------------------------------------------------------------
recv 367 bytes from udp/[192.168.4.4]:26000 at 13:31:50.111765:
------------------------------------------------------------------------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.4.4;rport=5060;branch=z9hG4bKggvjUH0rS99tc
Contact: <sip:bob@192.168.4.4:26000>
To: "Bob"<sip:bob@192.168.4.4>;tag=15c8325a
From: "alice"<sip:alice@192.168.4.4>;tag=cDg7NyjpeSg4m
Call-ID: YWEwYjNlZTZjOWZjNDg3ZjU3MjQ3MTA1ZmQ1MDM5YmQ.
CSeq: 130069219 BYE
User-Agent: X-Lite release 1014k stamp 47051
Content-Length: 0
bob 回送 OK,a-leg 释放完毕,通话结束。从下图可以很形象地看出 FreeSWITCH 的两条“腿”-- a-leg 和 b-leg。
整个呼叫流程图示如下:
bob (UAC) [ UAS-UAC ] (UAS) alice
| | | |
| INVITE | | |
|-------------------->| | |
| 100 Trying | | |
|<--------------------| | |
| 407 Authentication Required |
|<--------------------| | |
| ACK | | |
|-------------------->| | |
| INVITE | | |
|-------------------->| | |
| 100 Trying | | INVITE |
|<--------------------< >-------------------->|
| | | 100 Trying |
| | |<--------------------|
| 183 Progress | | 180 Ringing |
|<--------------------< |<--------------------|
| | | 200 OK |
| | |<--------------------|
| 200 OK | | ACK |
|<--------------------< >-------------------->|
| ACK | | |
|-------------------->| | |
| |
| Call Connected |
| |
| | | BYE |
| | |<--------------------|
| BYE | | 200 OK |
|<--------------------< >-------------------->|
| 200 OK | | |
|-------------------->| | |
| | | |
从流程图可以看出,右半部分跟上一节“UA间直接呼叫”一样,而左半部分也类似。这就更好的说明了实际上有 4 个 UA (两对)参与到了通信中,并且,有两个 Session。
再论 SIP URI
上面我们介绍了一些 FreeSWITCH 的基本概念,并通过一个真正的呼叫流程讲解了一下 SIP。由于实验中所有 UA 都 运行在一台机器上,这可能会引起迷惑,如果我们有三台服务器,那么情况可能是:
/---------------\
| FreeSWITCH |
| 192.168.0.1 |
\ --------------/
sip:bob@192.168.0.1 / \ sip:alice@192.168.0.1
/ \
/ \
/-----------------\ /-----------------\
| bob | | alice |
| 192.168.0.100 | | 192.168.0.200 |
\-----------------/ \-----------------/
sip:bob@192.168.0.100 sip:alice@192.168.0.200
alice 注册到 FreeSWITCH,bob呼叫她时,使用她的服务器地址,即 sip:alice@192.168.0.1,FreeSWITCH 接到请求后,查找本地数据库,发现 alice 的实际地址是 sip:alice@192.168.0.200,便可以建立呼叫。
SIP URI 除使用 IP 地址外,也可以使用域名,如 sip:alice@freeswitch.org.cn。更高级也更复杂的配置则需要 DNS 的 SRV 记录,在此就不做讨论了。