第四章 SIP協議

Seven Du發表於2013-04-25

SIP協議是FreeSWITCH的核心協議。講清楚SIP需要很大篇幅,甚至是整本書。本書是關於FreeSWITCH的,重點不在SIP。因此,我將僅就理解FreeSWITCH 必需的一些概念加以通俗的解釋,更嚴肅一些的資料請參閱其它資料或相關RFC(Request For Comments,如 RFC3261)。

SIP的概念和相關元素

會話初始協議(Session Initiation Protocol)是一個控制發起、修改和終結互動式多媒體會話的信令協議。它是由 IETF(Internet Engineering Task Force,Internet工程任務組)在 RFC 2543 中定義的。最早釋出於 1999 年 3 月,後來在 2002 年 6 月又釋出了一個新的標準 RFC 3261。

SIP 是一個基於文字的協議,在這一點上與 HTTP 和 SMTP 相似。我們來對比一個簡單的 SIP 請求與 HTTP 請求:

GET /index.html HTTP/1.1

INVITE sip:seven@freeswitch.org.cn SIP/2.0

請求由三部分組成。在 HTTP 中(第1行), GET 指明一個獲取資源(檔案)的動作,而 /index.html 則是資源的地址,最後是協議版本號。而在 SIP 中(第3行),INVITE 表示發起一次請求,seven@freeswitch.org.cn 為請求的地址,稱為 SIP URI,第3部分也是版本號。其中,SIP URI很類似一個電子郵件地址,其格式為“協議:名稱@主機”。與httphttps 相對應,有sipsips,後者是加密的;名稱可以是一串數字的電話號碼,也可以是字母表示的名稱;而主機可以是一個域名,也可以是一個IP地址。

SIP 是一個對等的協議,類似 P2P。不像傳統電話那樣必須有一箇中心的交換機,它可以在不需要伺服器的情況下進行通訊,只要通訊雙方都彼此知道對方地址(或者,只有一方知道另一方地址),如下圖,bob 給 alice 傳送一個 INVITE 請求,說“Hi, 一起吃飯吧...”,alice 說"好的,OK",電話就通了。

SIP點對點通訊

在 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 之間的路由選擇功能。

還有一種 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 只能“背對背”不停地工作。

B2BUA

從上圖可以看出,四個人其實全是 UA。從上面故事可以看出,雖然 FreeSWITCH 是 B2BUA,但也可以經過特殊的配置,實現一些代理伺服器和重定向伺服器的功能,甚至也可以從中間劈開,兩邊分別作為一個普通的 UA 來工作。這沒有什麼奇怪的,在 SIP 世界中,所有 UA 都是平等的。具體到實物,則 M 和 W 就組成了實現軟交換功能的交換機,它們對外說的語言是 SIP,而在內部,它們則使用自己家的語言溝通。bob 和 alice 就分別成了我們常見的軟電話,或者硬體的 SIP 話機。

這裡還有一個概念,叫做邊界會話控制器(SBC,Session Border Controller)。它主要位於一堆伺服器的邊界,用於隱藏內部伺服器的拓撲結構,抵禦外來攻擊等。SBC可能是一個代理伺服器,也可能是一個B2BUA。

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,在這裡,我們使用的 SIP 承載方式是 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 。

FreeSWITCH 需要驗證 alice 的身份才允許她註冊。在 SIP 中,沒有發明新的認證方式,而是使用已有的 HTTP 摘要(Digest)方式來認證。這裡它給alice傳送401訊息。

------------------------------------------------------------------------
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

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 的電話正在振鈴:

SIP UA間直接呼叫

詳細的呼叫流程圖為:

        bob               alice
        |                     |
        |    INVITE alice@example.com
        |-------------------->|
        |    100 Trying       |
        |<--------------------|
        |    180 Ringing      |
        |<--------------------|
        |    200 OK           |
        |<--------------------|
        |    ACK              |
        |-------------------->|
        |                     |
        |<======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 上(由 Contact 欄位給出。實際上,它預設在埠也是 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 負責,它包含了掛機原因(Reason: 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。

B2BUA的兩條腿

整個呼叫流程圖示如下:

    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, talking ...           |
     |                                               |
     |                     |   |     BYE             |
     |                     |   |<--------------------|
     |   BYE               |   |    200 OK           |
     |<--------------------<   >-------------------->|
     |   200 OK            |   |                     |
     |-------------------->|   |                     |
     |                     |   |                     |

從流程圖可以看出,右半部分跟上一節“UA間直接呼叫”一樣,而左半部分也類似。這就更好的說明了實際上有 4 個 UA (兩對)參與到了通訊中,並且,有兩個 Session。

再論 SIP URI

上面我們介紹了一些 FreeSWITCH 的基本概念,並通過一個真正的呼叫流程講解了一下 SIP。由於實驗中所有 UA 都 執行在一臺機器上,這可能會引起迷惑,如果我們有三臺機器,其中 192.168.0.1 是 FreeSWITCH 伺服器,而 bob 和 alice 分別在另外兩臺機器上,那麼情況可能是:

                    /---------------\
                    |  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 的實際地址(Contact)是 sip:alice@192.168.0.200,便可以建立呼叫。

SIP URI 除使用 IP 地址外,也可以使用域名,如 sip:alice@freeswitch.org.cn。更高階及更復雜的配置則可能需要 DNS 的 SRV 記錄,在此就不做討論了。

相關文章