SIP協議詳解(中文)-4

From: Alice <sip:[email protected]>;tag=1928301774
Call-ID: a84b4c76e66710
Cseq : 63104 OPTIONS
Contact: <sip:[email protected]>
Accept: application/sdp
Content-Length: 0
11.2 處理OPTIONS請求
給OPTIONS的應答的構造遵循標準的構造規則(8.2.6節描述)。應答碼的選擇必須和處理INVITE請求一樣的應答碼(就像處理INVITE請求一樣的返回)。這就是說,200(OK)應當在UAS能夠接收請求的時候返回,486(忙)應當在UAS如果忙的時候返回。這樣OPTIONS可以用來檢測UAS的基本狀態,這就是說,我們可以用OPTIONS來知道UAS能否接收INVITE請求。
在一個對話中的OPTIONS請求會產生一個200(OK)的應答,這是和在對話外創建的並且對對話沒有任何影響的請求相同。
這個OPTIONS的使用有一定的限制,因爲對於proxy處理OPTIONS和INVITE請求的不同。一個分支的INVITE可以有多個200(OK)的應答返回,但是一個分支的OPTIONS只能有單個200(OK)應答返回。因爲這是由於proxy處理OPTIONS請求是當作非INVITE的處理。參見16.7節有詳細的說明。
如果OPTIONS請求的應答是由proxy服務器給出的,proxy返回一個200(OK)的應答,列出這個服務器的各種選項和能力。
應答沒有消息體
Allow,Accept,Accept-Encoding,Accept-Language,和Supported頭域應當在200(OK)應答中出現。如果這個是由proxy產生的應答,那麼Allow頭域應當忽略,因爲proxy是方法無關的(也就是說不知道該如何處理方法的)。Contact頭域可以在200(OK)的應答中出現,並且與3xx應答有相同的語義。這就是說,他們可以列出指向客戶的一串名字和方法的集合(用以轉發請求)。一個Warning頭域是可以存在的。消息體也可以存在,消息體的類型是由OPTIONS請求的Accept頭域指明的(application/sdp是缺省的,如果Accpet頭域不存在的話)。如果Accept頭域中包含了一個類型能描述媒體接收能力,UAS應當在應答中包含一個消息體用於這個用途。詳細的application/sdp包體說明在[13]中描述。
UAS生成的OPTIONS應答例子。(對應11.1節中的請求例子)
SIP/2.0 200 OK
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKhjhs8ass877
; received = 192.0.2.4
To: <sip:[email protected]>;tag=93810874
From: Alice <sip:[email protected]>;tag=1928301774
Call-ID: a84b4c76e66710
Cseq: 63104 OPTIONS
Contact: <sip:[email protected]>
Contact: mailto:[email protected]
Allow: INVITE,ACK,CANCEL,OPTIONS,BYE
Accept: application/sdp
Accept-Encoding: gzip
Accept-Language: en
Supported: foo
Content-Type: application/sdp
Content-Length:274
(SDP not shown)
12 對話(Dialog)
一個UA的核心概念就是對話。對話是表現爲兩個用戶代理(UA)之間的持續一段時間的點對點的SIP關係。對話(Dialog)使得用戶代理之間的消息順序傳遞和兩個用戶代理之間的請求正確路由更加容易。對話(Dialog)可以認爲是對SIP消息解釋的上下文關係。第8節講述了方法無關的UA處理和響應對話(Dialog)外的請求。本節將討論如何通過請求和應答來創建一個對話(Dialog),並且在對話(Dialog)中如何發起和響應後續的請求。
一個對話在參與對話的UA中都有一個dialog ID作爲標記,這個ID由Call-ID,和一個本地tag和遠程tag組成。各個UA的dialog ID在對話中是不一樣的。特別是,在一邊UA的本地tag,在另外一方就是遠程tag。這些tag都是互相不透明的,並且使得整個dialog ID是唯一的。
dialog ID同樣是和所有的To頭域中包含了tag參數的請求及應答相關。
填寫一個消息中的dialog ID的規則依賴於SIP元素是UAC還是UAS。對於UAC來說,dialog ID中的Call-ID的值會填寫到消息中的Call-ID域中,遠程tag放在消息中的To的tag參數中,本地tag放在From的tag參數中。(這些規則對請求和應答都適用)。對於UAS來說,dialog ID的Call-ID值放在消息的Call-ID頭域中,遠程tag放在From頭域的tag中,本地tag放在To頭域的tag參數中。
一個對話包含一些特定的狀態用於以後的對話中的消息傳送。這個狀態由dialog ID,本地序列號(用來排序UA到對方的請求的序列),遠程序列號(用來排序請求從遠端到本UA),本地URI,遠端URI,remote target,一個布爾類型的標記”secure”,路由集合(一組有序的URI)組成。
路由集合是由發送請求到對方需要途徑的一組服務器列表組成。一個對話可以處於”early”狀態,這是由於當這個對話收到了臨時應答而創建,並且當收到了2xx終結應答的時候轉換到”confirmed”狀態。對於其他應答,或者沒有應答,”early”對話將會終結。

12.1 創建一個對話
對話是由對一組特定請求的沒有失敗的應答來創建的。在本規範中,只有包含To tag的2xx和101-199應答,並且請求是INVITE的,會建立一個對話。當收到一個非終結應答的時候,對話會建立成”early”狀態,並且成爲early dailog。創建對話的時候可以使用Extension來定義擴展。13節描述了INVITE請求的更多細節。在這裏,我們描述與方法無關的對創建對話狀態的處理。
UA必須按照下邊描述的方法對dialog ID進行賦值。
12.1.1 UAS行爲
當UAS響應一個請求給出一個應答,並且這個應答會建立一個對話的時候(比如對INVITE的2xx應答),UAS必須拷貝所有的請求中的Record-Route頭域到應答中去(包括URI,URI參數,和其他任何Record-Route頭域的參數,無論UAS是不是認識的參數都需要原樣拷貝),並且必須維持這些參數的順序。UAS必須增加一個Contact頭域給應答。這個Contact頭域包含一個UAS在後續對話請求中接收請求的地址(這個包含了給INVITE請求的2xx應答的ACK請求處理的地址)。通常情況下,UAS會用IP地址或者FQDN形式來發布自己的這個Contact地址。這個在Contact頭域中的URI必須是一個SIP或者SIPS URI。如果創建對話的請求在Request-URI中包含的是SIPS URI,或者在Record-Route頭域的最上的一個值是SIPS URI,或者如果請求中沒有Record-Route頭域但是請求中的Contact頭域是SIPS URI,那麼給出的應答中的Contact頭域必須是一個SIPS URI。 這個URI應該是全局有效的(就是說,這個URI可以用於對話外的消息)。同樣的,在請求INVITE中的Contact頭域的URI也不應當僅限於這個對話中使用。因此它可以用於對話外的消息中。
UAS接着創建這個對話的狀態。對話狀態必須維持直到對話結束。
如果請求是通過TLS過來的,並且Request-URI包含一個SIPS URI,”secure”標誌將被賦值成爲TRUE。
路由集合必須設置成爲請求中的Record-Route的URI列表,保留所有的URI參數和順序。如果請求中沒有Record-Route頭域,那麼路由集合必須設置成爲空。這個路由集合,即便是空的,爲了以後的對話中的請求,也要覆蓋任何預先存在(pre-existing)的路由集合。remote taget必須設置成爲請求的Contact頭域中的URI。
遠程序列號必須設置成爲請求中的Cseq頭域的序列號。本地序列號必須設置成爲空。dialog ID中的呼叫標誌應該設置成爲請求的Call-ID頭域的值。dialog ID的本地tag必須設置成爲對請求的應答包中的To頭域的tag,並且dialog ID的遠程tag必須設置成爲請求中的From 頭域中的tag。UAS必須能夠處理接收到的請求中的From頭域沒有tag標誌,在這種情況下,這個tag就是空值。這是爲了兼容RFC2543協議,它並沒有定義From tag。
遠程URI(remote URI)必須設置成爲From頭域中的URI,並且本地URI必須設置成爲TO頭域中的URI。
12.1.2 UAC行爲
當一個UAC發出一個請求,這個請求能夠建立一個對話(比如這個請求是INVITE),它必須在Contact頭域中提供一個基於全局的SIP或者SIPS URI(例如,可以在對話外使用的SIP URI)。如果請求包含一個Request-URI或者最上的Route頭域是SIPS URI,Contact頭域也必須包含的是SIPS URI。
當一個UAC接收到應答,並且這個應答建立對話的時候,它也同樣構造這個對話的狀態。這個狀態必須維持到對話的結束。
如果這個請求是基於TLS發送的,並且Request-URI包含一個SIPS URI,那麼”secure”標誌被設置成爲TRUE。
路由集合必須設置成爲應答中的Record-Route頭域的URI列表,保留所有的URI參數和順序。如果在應答中沒有Record-Route頭域,那麼這個路由集合必須設置成爲空集合。這個路由集合即便是空的,爲了以後的對話中的請求,也要覆蓋任何預先存在(pre-existing)的路由集合。remote taget必須設置成爲應答中的Contact頭域的URI。
本地序列號必須設置成爲請求中的Cseq頭域的序列號。遠程序列號必須設置成爲空(他會由遠端的UA在對話中發送請求而建立)。dialog ID中的呼叫標誌必須設置成爲請求的Call-ID頭域的值。dialog ID的本地tag必須設置成爲請求中的From頭域的tag,dialog ID的遠程tag必須設置成爲應答中的To頭域的tag。UAC必須能夠處理接收到的應答的To頭域中沒有tag的情況,在這個情況下,tag值取值成爲空。這是爲了能夠向下兼容RFC2543,它沒有規定To的tag。
remote URI必須設置成爲To頭域的URI,local URI必須設置成爲From頭域的URI。

12.2 對話中的請求
當兩個UA之間的對話建立以後,他們都可以在對話中初始化一個新的事務(transaction)。如果UA發送請求,將遵循UAC的事務規則。UA接收請求將遵循UAS的規則。在建立對話的事務過程中,UA扮演的角色可能是不一樣的。
在對話中的請求可以包含Record-Route和Contact頭域。不過,雖然他們會修改remote target的URI,但是這些請求也不會導致對話的路由集被改變。明確說,如果請求不是刷新target的請求,那麼這個請求不會更改對話的remote target URI,如果請求是刷新target的請求,那麼這個請求才會更改對話的remote target URI。對於用INVITE建立的對話來說,唯一的能夠刷新target的請求就是re-INVITE(見14節說明)。可能會有其他擴展定義通過其他方法來刷新target的請求。
注意ACK不是一個刷新target的請求。
刷新target請求只會更改對話的remote target URI,並且更改由Record-Route指定的路由集合。如果更新路由集合會帶來嚴重的和RFC2543向後兼容問題。

12.2.1 UAC行爲
12.1.1.1 產生請求
在對話中的請求是通過用許多對話的狀態部分來構造的。在TO頭域中的URI部分必須設置成爲對話狀態中的remote URI。To頭域的tag參數必須設置成爲dialog ID中的remote tag部分。請求的From URI必須設置成爲對話狀態中的local URI。From頭域的tag參數必須設置成爲dialog ID的local tag部分。如果remote或者local tag是空值,那麼tag參數必須分別從From或者To頭域中去除。
在請求序列中的原始請求的To和From頭域的URI的使用方法是爲了向下兼容RFC2543協議的,在RFC2543協議中,使用URI作爲對話的標誌。在這個規範中,只有tags用於區分對話。有可能在本協議的後續版本中,在對話中的請求必須強制反應原始請求的To和From頭域的URI將會去除。
請求的Call-ID必須設置成爲對話的Call-ID。在對話中的請求必須嚴格遵循單個遞增的Cseq序列號(每次增加1)(當然要除了ACK和CANCEL,這兩個請求中的Cseq必須和原始的請求或者確認請求一樣)。因此,如果本地序列號(local sequence number)不爲空,那麼本地序列號碼必須依次增加1,並且這個數值要存放到Cseq頭域中。如果本地序列號碼是空的,那麼在8.1.1.5節約定的初始值必須填寫進去。在Cseq頭域中的method字段必須和請求的方法(method)一致。
通過使用32位的長整數,使得即使每秒種產生1筆請求,也會要136年纔會用完這個整數出現重複。這個序列號的初始值的選取是爲了讓對話中後續的請求序列號不會重複。非0的初始值可以考慮採用時間來作爲初始的序列號。一個客戶端可以用31位有符號整數或者32位無符號整數來存放時間作爲初始化的序列號。
UAC使用remote target和路由集合來構造請求中的Request-URI和Route頭域。如果路由集合是空的,那麼UAC必須把remote target URI放到Request-URI中,並且UAC不能添加Route頭域到請求中。
如果路由集合不爲空,並且路由集合的第一個URI包含lr參數(見19.1.1),那麼UAC必須填寫remote target URI到Request-URI,並且必須包含Route頭域,這個Route頭域按照順序填寫路由集合和路由集合的參數。
如果路由集合不爲空,並且路由集合的第一個URI沒有包含lr參數,那麼UAC必須把第一個URI放在Request-URI中,並且拆去所有不被Request-URI允許的參數。UAC必須增加一個Route頭域順序包含所有剩下的路由集合元素,及其參數。UAC接着必須把remote target URI放在Route頭域的最後一項。
例如,如果remote targe是: sip:user@remoteua 並且路由集合包括:
<sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>
那麼請求應該有下列的Request-URI和Route頭域
METHOD sip:proxy1
Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua>
如果路由集合的第一個URI不包含lr參數,那麼對應的說明proxy並不能支持本文檔所約定的路由機制,而是支持RFC2543文檔所約定的路由機制,那麼在發送信息的時候需要通過替換Request-URI爲接收到的第一個Route頭域的值。將Request-URI的值放在Route頭域的目的是爲了保護Request-URI,使得它經過嚴格路由的時候不丟失(當請求遇到一個鬆散路由的時候會返回到Request-URI中?????。)
在對話內的任何一個刷新target的請求中,都應當包含一個Contact頭域,並且這個URI除非有必要,否則都應當是和對話內上次請求的URI值一樣。如果”secure”標誌設置成爲TRUE,那麼URI也應當是SIPS URI。
如果在12.2.2節討論的那樣,在刷新target請求中的Contact頭域會更新remote target URI。這個允許UA提供一個新的聯繫地址(Contact address),表明它在對話中改變了自己的地址。不過,如果請求不是刷新target的請求,那麼不會影響對話中的remote target URI。請求中的剩下的部分請按照8.1.1節描述的填寫。一旦請求被創建了,請求將按照對話外請求發送標準步驟(8.1.2節)來解析服務器的地址並且發送請求。
8.1.2節中的步驟一般把請求發送到Route頭域的最上一個地址,或者如果沒有Route頭域,那麼就發送到Request-URI地址。由於受到特定的限制,這些步驟也允許把請求發送到另外一個地址(比如在route set中沒有的缺省的外發proxy)
12.2.1.2 處理應答
UAC將會從transaction層收到請求的應答。如果客戶端的事務層返回一個超時,這會等同於一個408(請求超時)的應答。UAC處理3xx應答的時候,在這個應答是在對話內的請求的應答的處理方法和在對話外的處理方法是一樣的。這個方法在8.1.3.4節中描述。需要注意的是,雖然UAC會嘗試新的地址(處理3xx應答的時候),但是它依舊使用對話內的路由集合來構造請求的Route頭域。
當UAC收到一個刷新target請求的2xx應答的時候,如果對話的remote target URI存在,那麼它必須用這個應答的Contact頭域的值來替換對話的remote target URI。
如果對話那的請求的應答是481應答(呼叫/事務不存在Call/Transaction Does Not Exits)或者一個408(請求超時),那麼UAC應當終止這個對話。並且UAC應當在請求完全沒有應答的時候(客戶端transaciton將會通知TU這個超時)客戶端transaction終止這個對話。
對於INVITE初始化的對話,終止對話需要發送一個BYE。
12.2.2 UAS行爲
在對話中發送的請求,就像其他請求一樣,是原子請求。如果UAS收到某個請求,所有的相關狀態要麼一起改變,要麼就一起不變。在某些請求中,請求會影響好幾個狀態(比如INVITE請求)。
UAS從transaction層收到請求。如果請求的To頭域有tag字段,UAS的處理核心需要校驗對話的ID,拿請求中的tag和現存的對話相比較。如果匹配成功,那麼就是一個在對話中的請求。在這種情況下,UAS首先使用8.2節中的對話外請求處理的步驟。如果請求To頭域包括了一個tag字段,但是對話的ID並不匹配現存的對話,UAS可能是因爲崩潰而重新啓動,或者收到了一個另外(可能是錯誤的)UAS(UAS可以構造To的tags,這樣UAS在災備恢復下,可以把這個tag看成它自己的)。還有一種簡單的可能是請求發送錯誤了。在這個基礎上,UAS可以選擇接受或者拒絕請求。在允許的情況下,儘量處理這些請求會提供災難恢復的機制。UAS如果希望支持這樣的特性就必須遵循一些原則,比如用始終使用單調遞增的Cseq序列號,甚至是在重新啓動之後也這樣,在重啓動後重建路由集合,處理越界的RTP時間戳和序列號等等。
如果UAS由於不希望重構對話而拒絕這個請求,它必須應答對方一個481(呼叫/事務不存在。Call / Transaction不存在)應答。
對於在對話中接收到的,那些不會用任何形式更改對話狀態的請求,比如OPTIONS請求,他們等同於在對話外的處理請求。
如果遠端的序列號(remote sequence number)是空的,它必須設置成爲請求中的Cseq頭域的序列號(sequence number)。如果remote sequence number不是空的,但是請求中的sequence number小於這個remote sequence number,請求就是非順序的,並且必須通過應答500(服務器內部錯誤)打回去。如果remote sequence number不是空的,並且請求中的序列號大於這個remote sequence number,請求就是按照順序的。這個請求中的Cseq的序列號可以比remote sequence number大不止1。在這種情況下,並非是錯誤的,並且UAS應當準備接收和處理比上次處理的請求Cseq值大於1 的請求。UAS必須設置remote sequence number成爲請求中的Cseq頭域中的序列號。
如果一個proxy廢棄掉一個UAC產生的請求,並且UAC重新遞交這個請求的時候。這個請求是會具有一個全新的Cseq序列號。UAS是不會收到第一個請求的,這樣,Cseq序列號就會出現間隔,這樣的間隔並非是一種錯誤的情況。
當UAS接收到一個target刷新請求的時候,如果請求中存在Contact頭域,它必須用Contact頭域中的URI來替換對話的remote target URI。
12.3 終止對話
在建立對話中的終結對話,跟請求方法無關,如果對話外的請求產生了一個非2xx終結應答,任何前邊請求創建的”早期對話”(early dialogs)將會終止。在已經建立的對話中,終結對話就是請求方法相關的。在這個定義中,BYE方法將會終結一個對話。15節有細緻的討論。
13 初始化一個會話
13.1 概覽
當UAC希望初始化一個會話(比如,audio,video或者遊戲),它首先構造一個INVITE請求。這個INVITE請求一個服務器來建立一個會話。這個請求可能會由proxy層層轉發,最後到達一個或者多個可能能夠處理這個邀請的UAS。這些UAS需要看看是否用戶接收這個邀請。然後UAS可以接收這個請求(也就是會話建立了),通過發送2xx應答。如果邀請被拒絕,根據拒絕的原因,3xx,4xx,5xx或者6xx應答將會發送。在發送終結應答之前,UAS可以發送一些臨時應答(1xx)應答給UAC,以便UAC能夠掌握建立會話的進度。
當收到了一個或者多個臨時應答,UAC可能收到一個或者多個2xx應答或者一個非2xx終結應答。由於在INVITE終結應答之前,可能有不少時間,在INVITE事務的可靠性機制和其他的請求不同(比如OPTIONS)。當UAC收到了終結應答,UAC需要給每一個INVITE的終結應答,發送一個ACK請求。發送ACK請求的步驟依賴於應答的類別。對於在300到699的終結應答,ACK是在transaction層處理的,並且遵循一系列規則(17節)。對於2xx應答,ACK是由UAC處理核心產生的。
INVITE的一個2xx應答會建立一個會話,同時也建立了一個基於發送INVITE請求的UA和產生2xx應答的UA之間的對話。因此,當從多個遠程UA收到了多個2xx應答(可能由於INVITE的分支),每一個2xx建立一個不同的對話(dialog)。所有這些對話都是同一個呼叫的組成部分。
本節介紹了INVITE請求建立會話的詳細過程。支持INVITE的UA也一定同時支持ACK,CANCEL和BYE。
13.2 UAC處理
13.2.1 創建一個初始化的INVITE
由於初始化的INVITE請求是一個對話外的請求,它遵循8.1.1節的步驟創建。除此之外還有專門針對INVITE的附加處理步驟。
在INVITE中應當包括一個Allow頭域(20.5節)。它用來標誌在這個INVITE建立的這個對話(dialog)中什麼樣的方法可以接受。比如,一個UA可以在對話中接收和處理INFO請求[34],那麼在INVITE請求的Allow頭域中應當列出這個INFO方法。在INVITE請求中,Supported頭域應當包含。這個頭域包含了所有這個UAC支持的擴展部分。
在INVITE中可以包含一個Accept頭域(20.1節)。這個標誌了UA在後續建立的對話中,能兼容的接收和發送的Content-Type。Accept頭域支持不同會話描述格式(session descrioption format)的時候特別有用。
UAC可以通過包含一個Expire頭域(20.19節)來限制邀請的有效期限。如果Expire頭域的時間到了還沒有接收到INVITE的終結應答,UAC處理核心應當像9節描述的那樣產生一個對INVITE請求的CANCEL請求,
UAC還可以根據需要增加Subject(20.36節),Organization(20.25節)和User-Agent(20.41節)頭域。這些頭域都包含了INVITE的相關資料。UAC可以給INVITE增加一個消息體。8.1.1.10節講述瞭如何構造Content-Type頭域來描述消息體。
對於消息體,有一些特別的規定――他們是基於某種磋商機制的,他們對應的Content-Disposition 是”session”(會話的)。SIP使用一個請求/應答模型,UA發出一個會話描述,稱作是請求,裏邊包含了會話的描述。這個請求標誌了特定的聯繫內涵(比如audio,vidio,game),這些內涵的參數(比如解碼器等等),並且從應答方接收媒體信息的地址。對方UA會迴應另外一個會話的描述,稱之爲應答,標誌了能接受的聯繫內涵,這些內涵的參數。這個請求/應答的交換實在對話的上下文進行中的,所以如果一個SIP INVITE請求導致了多個對話,每一個對話都包含自己獨立的請求/應答的交換。請求/應答模型定義了對於請求和應答的限制。(比如在上一個請求尚未處理完成情況下不能發起下一個請求)。這也導致了請求/應答在SIP消息中出現的位置限制。在這個規範中,請求和應答只能出現在INVITE、ACK請求和其應答中。請求和應答的使用中更進一步被限制。在初始化一個INVITE事務中,規則如下:
o 初始化請求必須在INVITE中,如果不在INVITE請求中,就必須在UAS回送給UAC的第一個非失敗的可靠消息中。在這個規範中,這個應答就是2xx應答。
o 如果初始的請求是一個INVITE,那麼應答必須是由UAS發送回給對應發出INVITE請求的UAC的可靠的非失敗的消息。在本規範中,只有2xx應答對應這個INVITE請求。同樣相同的應答可能在之前發送的零食應答中存在。UAC必須把它接收到的第一個會話描述當作是應答,並且必須忽略任何在初始INVITE請求中後續的會話描述應答描述。
o 如果初始請求是在第一個可靠的非失敗的UAS回送給UAC的消息中,那麼應答必須在這個消息的確認消息中(在本規範中,就是給2xx應答的ACK確認消息)
o 在發送或者接收到第一個請求的應答之後,UAC可以同樣依據這樣的問答方法產生後續的請求。但是只能在收到每一個請求的應答之後才能發起下一個請求。不能在上一個請求尚未收到應答的時候發起下一個請求。
o 當UAS發送或者接收到初始化的請求的時候,禁止在它給初始的INVITE請求的應答中產生後續的請求(協商會話描述請求)。這就意味着基於本規範的UAS在完成初始化的事務之前,不會產生任何會話描述請求。

具體來說,根據本規範,上邊的規則分別定義了兩種UA之間交換信息的方法。請求實在INVITE中,應答是在2xx(可能在1xx中也存在,具備相同的值)中,或者請求在2xx中,應答在ACK中。(這個意思是說,兩個UA之間建立連接的時候,首先需要協商一下兩個UA能夠支持的消息體正文,那麼這個協商關係也是通過問答形式的,也就是通過請求/應答的,這個媒體磋商的請求既可以在UAC發起的INVITE請求中,也可以在UAS迴應的2xx應答中。同樣的,媒體磋商的應答既可以在UAS的2xx應答或者1xx應答中,也可以在ACK確認請求中)。所有的支持INVITE請求的UA都必須支持
兩種交換方式。會話描述協議(session description protocol sdp)(RFC 2327[1])在所有的UA中都必須得到支持,並且它的用法和請求/應答的構造必須遵循[13]中定義的步驟。
在上邊講述的請求/應答模型中,只能適用於在包頭域Content-Disposition中的值是”session”的包體情況。因此,有可能會INVITE和ACK請求中都包含一個包體信息(比如,INVITE包含一個相片(Content-Disposition:render)並且ACK包含一個會話描述(Content-Disposition:session))。
如果Content-Disposition頭域不存在,Content-Type 是application/sdp的包體實現就等同於Content-Disposition”session”,其他Content-Type的情況就是實現”render”。
當INVITE請求創建以後,UAC遵循對話外請求發送的步驟進行發送(8節)。這也就是創建一個客戶事務並且由這個客戶事務發送請求並且處理應答。
13.2.2 處理INVITE應答
當INVITE請求被傳送給INVITE的客戶事務層進行處理,UAS等待INVITE的應答。如果INVITE客戶事務層返回一個超時而不是收到一個應答,那麼這個TU就應當像收到一個408(請求超時)應答(8.1.3節)那樣進行處理。
13.2.2.1 1xx應答
有可能在收到一個或者多個終結應答之前,UAC會收到0個或者1個或者多個臨時應答。INVITE的臨時應答會建立”early dialogs”(早期對話)。如果一個臨時應答在To頭域中有一個tag子頓,並且應答的dialog ID並不是已經存在的對話的ID,那麼就應當遵循12.1.2節定義的步驟創建一個對話(早期對話)。
early dialog只會在下邊這個情況中需要:如果一個UAC需要在完成初始的INVITE事務之前,給對方發送一個對話內的請求的時候,就需要early dialog。在臨時應答中的頭域可以在當對話是early state的時候都有效(也就是說,比如一個臨時應答的Allow 頭域包含的方法,在對話狀態是early state的時候都是有效的。[由於Allow是允許的方法集合,所以,當對話狀態是早期對話的時候,這個Allow的集合是不會改變的,但是當創建正式的dialog之後,Allow的集合可能會改變哦]。)
13.2.2.2 3xx應答
一個3xx應答可能包含一個或者多個Contact頭域值,這個頭域值提供了被叫方可能存在的地點。UAC可以根據3xx應答的狀態碼(21.3節)來決定是否嘗試這些新的地址。
13.2.2.3 4xx,5xx,6xx應答
在INVITE請求中,可能會收到單個非2xx終結應答。4xx,5xx,6xx應答如果包含了Contact頭域,那麼這個頭域值指示了錯誤的詳細信息的解釋地點。後續的終結應答(只有可能在發生錯誤的情況下),必須被忽略掉。
所有的早期對話都會由於接收到非2xx終結應答而結束。
一旦接收到了非2xx終結應答,UAC處理核心就認爲INVITE事務結束了。INVITE客戶事務處理生成對這個應答的ACK(參見17節)。
13.2.2.4 2xx 應答
單個INVITE請求可能會導致多個2xx應答返回給UAC,這是因爲proxy可以分支。每一個應答都是由To中的tag參數來進行區分的,並且每一個應答都代表了一個獨立的對話,具備單獨的對話ID。
如果在2xx應答中的對話ID和一個現存的對話匹配,那麼這個對話必須切換到”confirmed”狀態,並且對話的路由集合必須基於2xx的應答進行重新計算(參見12.2.1.2)。如果不匹配,那麼必須創建一個新的對話,這個對話具備”confirmed”狀態,參見12.1.2的步驟進行創建。
注意在對話狀態中,只有路由集合不需要重新計算。其他部分比如對話內的最大的序列號(遠程的和本地的)等都不需要重新計算。路由集合只是由於需要向後兼容而需要重新計算。RFC 2543並沒有要求在1xx應答中反射Record-Route頭域回來,只在2xx請求中要求了。我們不能更新對話狀態的全部部分,因爲在早期對話(early dialog)中可能會存在對話中的請求,比如更改序列號等等。UAC核心必須爲每一個2xx應答,產生一個ACK請求。除了在Cseq和身份認證相關的頭域之外,ACK請求的頭域的創建和在對話中的請求創建的方法一樣(12節)。Cseq頭域的序列號部分必須和需要確認的INVITE請求一樣,但是Cseq的方法部分必須是ACK。ACK必須包含和INVITE請求相同的信任狀。如果2xx包含一個媒體磋商請求(基於上述的規則),ACK必須在包體中包含一個媒體磋商應答。如果2xx應答的媒體磋商請求不能被接收,UAC核心必須產生一個有合法的應答ACK,並且立刻發送一個BYE請求。

當ACK創建以後,[附件4]中規定的步驟用來檢測對方地址,端口和transport。這個請求是直接交給通訊層進行通訊的,而不是交給一個客戶事務層進行發送。這是由於UAC核心直接處理ACK的重發,而不是事務層進行重發的處理。每次收到一個重發的2xx終結應答的時候都必須發送一個ACK到通訊層。

UAC核心認爲INVITE事務在接收到第一個2xx應答後的64×T1秒後完成。在這個時間點後,所有沒有轉換成爲建立連接狀態的早期對話都會被終止。一旦UAC確認INVITE事務完成了,那麼缺省認爲不會收到新的2xx應答了。如果,在相應了對INVITE請求的全部應答之後,UAC並不希望創建這個對話,那麼UAC必須通過15節描述的那樣發送BYE請求來結束對話。
13.3 UAS處理
13.3.1 處理INVITE
UAS核心從事務層收到INVITE請求。首先根據8.2節定義的步驟進行處理請求,8.2節中定義的是跟對話內外無關的請求的處理。如果處理順利完成(沒有產生應答),UAS核心根據如下步驟進行額外處理:
1、    如果INVITE請求包含一個Expires頭域,UAS核心就設置一個時鐘計數=這個頭域值。如果時鐘到了,這個邀請就過期了。如果在UAS尚未產生終結應答的時候就超時了,那麼487(請求終止)應答應當產生給UAC。
2、    如果請求是一個對話中的請求,12.2.2節定義的方法無關的處理步驟將首先進行處理。這個處理可能會影響到會話;14節講述了細節。
3、    如果請求的To頭域包含了一個tag,但是對話的ID與現存的任何一個對話都不匹配,那麼UAS可能是由於崩潰而重新啓動的,或者是由於接收到了本應當發送給另外一個UAS的請求(或者就簡單是由於請求填寫錯誤)。12.2.2節提供了這種情況的處理指引。從這開始的處理將假定這個INVITE是在對話外的,並且INVITE請求的目的是建立一個新的會話。INVITE請求可能包含一個會話描述,在這種情況下是希望和UAS進行會話媒體的磋商。即使INVITE請求是對話外發出的,這個INVITE參與的用戶也有可能正是那個會話中的參與方。這個是由於在多方會議中,某個正在會議中的用戶,被其他參與方邀請參加。如果需要鑑別這樣的情況,UAS可以使用會話描述來檢查是否重複邀請。比如,SDP包含了會話的ID和版本號。如果這個用戶本身就是會話中的一方,並且session參數包含的會話描述沒有改變,UAS可能就悄悄接受這個邀請(就是說,在不提示用戶的情況下發送2xx應答)。
如果INVITE並沒有包含某個會話描述(session description),UAS就是被邀請創建一個會話,並且UAC已經希望UAS來提供這個會話offer。UAS必須在它的給UAC的第一個非失敗的可靠消息中提供這個offer。在本規範中,給INVITE請求的2xx應答中就應當提供這個offer。
UAS可以提示進度,接受,轉發,或者拒絕這個邀請。在這些情況下,它通過按照8.2.6節描述的步驟建立應答。
13.3.1.1 提示進度
如果UAS不能馬上接受或者拒絕邀請,那麼它可以提示某種形式的進度給UAC(比如提示一個回鈴聲等等)。這是通過一個101到199的臨時應答實現的。這些臨時應答建立了早期對話(early dialog)(通過8.2.6和12.1.1)。如果UAS願意,UAS可以發送多個臨時應答。每一個臨時應答都必須包含相同的dialog ID。這些臨時應答都並非可靠傳送的。
如果UAS打算延長一點時間來響應這個INVITE請求,它需要請求一個”extension”來防止proxy來取消這個事務。proxy有權利來取消超過3分鐘未完成的事務。要防止這個取消,UAS必須每分鐘發送一個非100臨時應答,防止由於1xx臨時應答的非可靠傳輸導致的臨時應答丟失。
如果呼叫出於等待狀態(比如用戶設置成爲呼叫等待的)或者這個呼叫正在和PSTN電話系統進行通訊(PSTN系統允許呼叫沒有應答),一個INVITE事務是可以被延長處理時間的。
13.3.1.2 INVITE請求轉發
如果UAS決定轉發這個呼叫,就需要發出3xx的應答。300(多重選擇),301(永久轉移),302(臨時轉移)應答中應當包含一個Contact頭域,這個頭域包含了一個或者多個表明需要重試的URI新地址。這個應答交給INVITE服務端事務層,由服務端事務層負責應答的重發。
13.3.1.3 INVITE請求的拒絕
拒絕INVITE請求的常見情景是被叫方不想或者不能在終端系統上接收這個呼叫。486(用戶忙)應當在這樣的情況下返回。如果UAS知道沒有其他終端系統能夠響應這個呼叫,就應當返回一個600(Busy Everywhere)。不過,通常情況下UAS是不太會知道這個情況的,並且這個應答也是罕見的。這些應答是交給INVITE服務端的事務層進行發送的,由這個事務層來保證應答的重發機制的。如果UAS拒絕的是INVITE請求包含的媒體磋商offer,UAS應當返回一個488(Not Acceptable Here)應答。這個應答應當包含一個Warning頭域來解釋爲何offer被拒絕。
13.3.1.4 接受INVITE請求
UAS核心產生一個2xx應答。這個應答建立一個對話,然後遵循8.2.6節和12.1.1節的描述進行處理。
響應INVITE請求的2xx應答包含Allow頭域和Supported頭域,並且可能包含Accept頭域。包含這些頭域的目的是爲了讓UAC不需要再次請求就能夠知道UAS的特性以及UAS的擴展支持。
如果INVITE請求包含了一個媒體磋商請求offer,並且UAS還沒有發送應答,2xx應答中必須包含針對這個offer的應答。如果INVITE請求沒有包含這個offer,而且UAS也尚未發出offer,2xx應答必須包含這個媒體磋商offer。
當應答構建好了以後,它會交給INVITE的服務端事務層進行發送。注意,INVITE的服務端事務將會由於收到這個終結應答並且交給通訊層進行發送而銷燬。因此,有必要在沒有收到ACK的時候,每隔一定的時間就直接交給通訊層進行發送。2xx交給通訊層進行發送的時間間隔是從T1秒開始,並且每次發送後就加倍,直到到達T2秒的時間間隔(T1和T2的時間間隔定義在17節)。當收到了針對這個應答的ACK請求之後,重發就終止了。這個是與使用什麼通訊協議來發送這個應答是無關的。
由於2xx的重發是端到端的,並且在UAS和UAC之間存在採用UDP通訊的節點。所以要保證通過這些節點進行可靠的傳送,就必須採用間隔時間重發的機制,哪怕UAS本身的通訊機制是可靠的。
如果服務端的對2xx應答的重發經過了64×T1秒還沒有收到ACK請求,那麼dialog就認爲是confirmed,但是會話卻應當終止。這個是用過15節描述的方法發送BYE請求來結束。
14 更改已經存在的會話
一個成功的INVITE請求(13節)既會創建一個基於兩個用戶之間的對話,也會基於請求/應答模式(offer-answer)創建一個會話。12節講述瞭如何通過target refresh 請求來修改一個現存的會話(比如,修改對話的remote target URI)。本節描述如何修改實際的會話(session)。
這個修改可以包括修改地址或者端口、增加媒體流、刪除媒體流等等。這是通過發起新的INVITE請求來完成的,並且這個新的INVITE請求是基於建立會話所相同的對話的。在一個現存對話中發出INVITE請求就是re-INVITE.
注意,單個的re-INVITE請求可以同時更改對話和會話的參數。
呼叫方或者被叫方都可以更改現存的會話。
在本協議中,UA檢測本地媒體有效性是基於自身的策略的。但是,我們並不建議自動產生re-INVITE或者BYE請求,因爲這樣可能會導致網絡上的阻塞。在任何情況下,如果某些消息將被自動發送,那麼他們應當等待一個隨機的時間間隔。
注意,上邊的這些描述是特指自動產生的BYE和re-INVITE。如果用戶由於媒體不兼容而掛機,UA應當正常發出BYE請求,而不視爲自動產生的BYE。
14.1 UAC行爲
與INVITE相同的會話描述磋商offer-answer模式(13.2.1節)在re-INVITE中也一樣採用。假設UAS希望增進一個媒體流,那麼UAC將會創建一個新的offer包含這個媒體流,並且發送INVITE請求給他的對方。特別需要注意的是,這個會話的全描述,而不是變化部分需要傳送。這個支持無狀態的會話處理,並且支持錯誤恢復機制。當然,UAC可以發送一個re-INVITE請求而不包含會話描述,在這樣的情況下,就是在這個re-INVITE的第一個可靠的非失敗的應答中將會包含這個會話描述offer(在這個規範中,就是2xx應答)。
如果會話描述格式具有版本號碼,那麼這個磋商的offer應當標誌這個變化了的媒體描述版本。
re-INVITE請求中的To,From,Call-ID,Cseq,Request-URI頭域應當和正常的在對話中的請求構造方法一樣(12節)。
在re-INVITE請求中,UAC可以選擇不增加一個Alert-Infor頭域或者具有Content-Disposition=”alert”的消息體。因爲UAS通常不會要求提示操作者來響應這個re-INVITE請求。
和INVITE不同的是,INVITE可以分支(分岔成爲多份INVITE),re-INVITE是不會分支的,所以,只會由一個單個的終結應答。re-INVITE不會分岔的原因是因爲Request-URI標誌的是建立對話的UA的目標地址,而不是用戶的address-of-record地址。
需要注意的是,在相同的對話中,UAC不能在上一個INVITE請求完成前(無論是那一方發起的INVITE)再次發起一個新的INVITE。
1、    如果有正在處理的INVITE客戶事務,TU必須等待這個事務終結或者完成,才能初始化一個新的INVITE。
2、    如果有正在處理的INVITE服務事務,TU必須等待這個事務確認或者終結,才能開始處理一個新的INVITE。
不過,UA可以在INVITE事務正在處理的同時,處理一個普通的事務。也可以在一個普通事務正在處理的同時來初始化一個INVITE事務。如果UA接收到一個針對re-INVITE的非2xx終結應答,則會話參數不能改變,應當就像沒有收到過這個re-INVITE請求一樣。注意,就像在12.2.1.2節一開始講的那樣,如果非2xx終結應答是一個481(Call/Transaction Does Not Exists),或者一個408(Request Timeout),或者完全沒有re-INVITE請求的應答(也就是說從INVITE客戶事務端返回一個超時),UAC會終止這個對話。
如果UAC收到一個re-INVITE的491應答,他應當啓動一個值爲T的時鐘,這個T的取值如下:
1、    如果UAC是這個dialog ID的Call-ID的擁有者。(也就是說是UAC產生的Call-ID),那麼T取值爲一個2.1到4秒的隨機數,單位是10毫秒。
2、    如果UAC並非是dialog ID的Call-ID的擁有者,T應當取值是0到2秒的隨機數,單位是10毫秒。
當這個時鐘到了,如果UAC還希望再次嘗試更改會話參數,UAC應當再次嘗試re-INVITE請求一次。這個意思是說,如果這個呼叫已經被BYE所掛掉了,那麼re-INVITE請求就沒有再發的必要。
發送re-INVITE請求的規則,以及針對re-INVITE請求產生的2xx應答而產生的ACK請求的發送規則,等同於初始的INVITE請求(13.2.1節)。
14.2 UAS行爲
13.3.1節描述了區分初始的INVITE請求和re-INVITE請求的方法,以及處理對現存的對話中處理re-INVITE請求的步驟。
UAS在發送第一個INVITE的終結應答之前,收到第二個INVITE請求,並且這個請求的Cseq序列號大於第一個INVITE請求,那麼就應當給第二個INVITE請求返回一個500(服務器內部錯誤)應答,並且必須包含一個Retry-After頭域,這個頭域中應當包含一個0-10秒的隨機數。
如果UAS正在處理一個INVITE請求的時候又收到了一個在同一個對話上的INVITE請求必須返回一個491(Request Pending)應答給接收到的INVITE。
如果UA接收到一個對現存的對話的re-INVITE請求,那麼就必須檢查有關會話描述(session description)的版本標誌(version identifiers),或者,如果沒有版本標誌,那麼就需要檢查會話描述的正文看看有沒有變化。如果會話描述改變了,UAS必須由此調整會話參數,在調整參數的時候可能會要求用戶確認。

會話描述的版本可以用來提供給會議的新近加入者,增加或者刪除媒體,或者由單點會議更改成爲多方會議。
如果新的會話描述是不能被UA接受的,UAS可以用返回一個488(Not Acceptable Here)來拒絕這個re-INVITE請求。這個響應應當包含一個Warning頭域(用來提供給請求方,提供這個拒絕的原因)。如果UAS產生一個2xx應答,但是沒有收到ACK,它應當產生一個BYE來結束這個對話。
UAS可以不產生180(Ringing)應答給re-INVITE,因爲UAC一般不展示這個信息給用戶。同樣的,UAS可以增加一個Alert-Info頭域或者一個由Content-Disposition“alert”的消息體來應答給re-INVITE來讓UAC展示這個信息。
如果UAS在2xx應答中提供了一個媒體磋商offer(因爲INVITE沒有包含一個offer),那麼它應當當作新呼叫一樣的構造這個offer,就像在[13]中SDP描述一樣,遵循發送更改現有的會話的offer的限制。特別需要注意的是,這個意味着它應當包含全部的UA支持的媒體類型和媒體格式。UAS必須確保會話描述在媒體格式,通訊方式,或者其他要求對方支持的參數上,新的會話描述和舊的會話描述一樣。這個可以避免對方拒絕這個會話描述。如果,UAC任然是不能接受這個會話描述,UAC應當產生一個它能夠接受的會話描述應答,並且發送一個BYE來結束這個會話。

15 結束一個會話
本節描述了結束由SIP建立的會話的步驟。會話的狀態和對話的狀態是密切相關的。當一個會話由INVITE建立的時候,每一個由不同UAS的1xx或者2xx的應答創建一個對話,並且當完成了會話描述的請求/應答(offer/answer)交互之後,它也就創建了一個會話。這就是說,每一個會話都和單個對話”相關”-會話是對話所創建的。如果初始化的INVITE產生了非2xx的終結應答,它也終結了由本次請求創建的任何會話(如果有的話),並且終結了所有的本次請求創建的對話(如果有的話)。由於事務完整性的保證,一個非2xx的終結應答同樣也防止了本次INVITE以後可能創建的會話。BYE請求用於終結指定的會話或者嘗試建立的會話。在這裏,特定的會話是一個和與之相對的對話的對方UA。當在對話中接收到了一個BYE,任何與該對話相關的會話都應當終止。UA禁止在對話外發送BYE請求。請求方UA可以在已經建立好的對話或者早期對話中發起BYE請求;被叫方只能在建立好的對話中發起BYE請求,不能在早期對話中發起BYE請求。
不過,在一個建立好的對話中,被叫方的UA不能在接收到對應2xx應答的ACK請求前發送BYE請求,或者不能在服務器事務超時前發送BYE請求。如果沒有SIP擴展定義了和這個對話相關的其他應用層狀態,這個BYE請求同樣結束了對話。

在對話和會話中,給INVITE的非2xx的終結應答,使得使用CANCEL比較有吸引力。CANCEL是嘗試強制給INVITE請求一個非2xx應答(比如,487應答)。因此,如果UAS希望放棄整個呼叫,它可以發送一個CANCEL。如果INVITE會有2xx終結應答,這個意味着UAS在CANCEL正在處理的時候,接收到一個邀請。UAC可以繼續用這個2xx應答建立會話,也可以用BYE終結這個會話。(這個意思是說,一般情況下,如果UAC希望cancel 這個INVITE請求,那麼就會發出CANCEL請求,如果接收到了非2xx的終結應答,就意味着CANCEL掉了,但是如果接收到的還是2xx應答,就說明沒有CANCEL掉,沒有CANCEL掉呢,就可以選擇繼續建立會話,或者說發送一個BYE來終結會話)

在SIP中,並沒有一個很好的”hangin up”(掛機中)定義。它屬於一個用戶界面的普通常見的細節。通常,當用戶掛機,它意味着結束建立會話的嘗試,並且終止所有已經建立的會話。對於呼叫方的UA來說,如果沒有收到初始INVITE請求的終結應答,這個可能是產生對初始INVITE請求的一個CANCEL請求,並且收到終結應答之後給每一個建立好的對話發出一個BYE。對於被叫方的UA,就是很普通的BYE;粗略來說,當用戶(因爲響應振鈴)摘機,就會產生一個2xx應答,於是掛機會在收到ACK請求之後發送一個BYE。這不是說在收到ACK之前用戶不能掛機,這只是表達在用戶的電話中的軟件,需要保持一小會兒狀態,來正確釋放狀態。如果某個UI(用戶界面)允許用戶在不摘機的情況下拒絕呼叫,可以用403(Forbidden)來作爲INVITE的應答,在這樣的情況下,BYE就不能發送。
15.1 使用BYE請求終止一個會話
15.1.1 UAC行爲
BYE請求就像其他在對話內的請求一樣的構造,參見12節的描述。
當BYE請求創建好了之後,UAC核心處理部分創建一個新的非-INVITE客戶端事務,並且用它來處理BYE請求。UAC必須認爲當BYE請求一發送到客戶端事務,會話就結束了(因此也就停止發送或者接收媒體流)。如果BYE請求的應答是481(Call/Transaction Does Not Exists)或者408(Request Timeout)或者BYE請求壓根沒有應答(就是說客戶端事務返回一個超時),UAC必須認爲會話和對話都已經結束。
15.1.2 UAS行爲
UAS首先按照通用的UAS接收到請求的處理步驟進行BYE請求的處理(8.2節)。UAS核心處理部分接收到BYE請求以後,首先檢查它是否和現存的對話匹配。如果BYE並不匹配現存的任何一個對話,那麼UAS應當產生一個481(Call / Transaction Does Not Exists )應答,並且傳送給服務器事務。
這個規則意味着如果UAC發送沒有帶tag標誌的BYE請求會被拒絕。這個是一個對RFC2543的改動,RFC2543允許BYE不帶tag標誌。
UAS核心處理部分接收到BYE請求,並且發現和現存的對話匹配,那麼它必須遵循12.2.2的步驟來處理請求。一旦處理完成,UAS應當終止這個會話(因此停止發送和接收媒體流)。唯一一個可以選擇不終止的情況是多方會話,在多方會話中參與者允許對話中的其他參與方終止他們自己的會話。不管是否終止會話中的參與方,UAS核心處理都必須給BYE產生2xx的應答,並且必須由服務器的通訊層進行傳輸。
UAS必須依舊響應在這個對話中接收到的未決的請求。我們建議用487(請求終止)來給這些未決的請求以應答。
16 proxy行爲
16.1 概述
SIP代理服務器是路由SIP請求到UAS的,並且路由SIP應答到UAC的。一個請求可能通過多個proxy到達UAS。每一個都會作出路由決定,在發送給下一個節點前對請求做一點修改。應答會通過和請求相同的proxy路徑,只是順序是逆序的。
proxy是一個SIP邏輯上的概念。當接收到一個請求,在做代理服務器之前,首先應該有一個部件來決定是否自身需要響應這個請求。例如,在作爲代理服務器處理請求之前,首先判定請求可能是非法的或者請求需要一個信任狀。這個元素可以使用任何合適的錯誤代碼來響應這個請求。當直接應答請求的時候,這個元素(proxy)將作爲UAS角色,並且必須遵循8.2節描述的UAS行爲規範。
proxy對於每一個新的請求來說,既可以作爲有狀態的也可以作爲無狀態的模式來處理。當作爲無狀態的處理模式的時候,proxy就是簡單的轉發。它轉發每一個請求下行到一個由請求所決定的目的地。並且簡單轉發從上行流取得的應答。一個無狀態的proxy在處理完一個消息之後就會丟棄這個消息的相關資料。有狀態的proxy會保留這些信息(尤其是事務信息),保留每一個接收的請求和每一個接收請求的應答的相關信息。它保留這些信息用於處理與這個請求相關的後續消息。一個有狀態的proxy可以選擇”分支”一個請求,路由它到多個地點。任何被路由到多個地點的請求都必須當作有狀態的處理。在某些情況下,proxy可以用有狀態的通訊協議(比如TCP)來轉發請求,而不用自身成爲事務有狀態的。例如,proxy可以簡單轉發請求從一個TCP連接到另外一個TCP連接,而不用自身作爲有狀態的,只要它放置足夠的信息在需要轉發的消息裏,使得能夠正確把應答發送到接收到對應請求的連接發送出去。如果在不同傳輸通訊協議之間進行請求的轉發,那麼就必須要求proxy的TU採用某種手段來保證可靠的從一個協議有狀態的轉到另一個協議。
有狀態的proxy可以在處理請求中的任何時候轉換成爲無狀態的,只要它不作任何可能導致不能無狀態的操作(比如分支,比如產生100應答)。當做這樣的轉換的時候,所有的狀態就只是簡單的廢棄掉。proxy不應當發起一個CANCEL請求。
在作爲無狀態的或者有狀態的時候,處理請求的步驟是一樣複雜的。接下來的步驟是從有狀態的proxy角度來些的。最後一節是講述無狀態proxy的區別。
16.2 有狀態的proxy
作爲有狀態的proxy,它必須是一個純粹的SIP事務處理引擎。它在這裏的定義遵循17節講述的服務端和客戶端的事務處理規定。有狀態的proxy有一個服務端事務,這個事務與一個或者多個客戶端事務相關,這些客戶端事務是由高層proxy處理元素產生的(圖3),這些高層proxy處理元素就是proxy處理核心。一個輸入的請求是通過一個服務端事務來處理的。請求由服務端事務交給proxy處理核心進行處理。proxy處理核心檢查請求應當路由到哪裏,選擇一個或者多個下一個節點。每一個發往下一個節點的外發請求都由客戶端事務進行處理。proxy處理核心從客戶端事務中得到請求的應答並且把他們的應答交給服務端事務進行發送。
有狀態的proxy爲每一個接收到的新的請求創建一個服務端事務。任何請求的重複都是由這個服務端事務來處理(參見17節)。proxy處理核心必須遵循UAS的模式,發送一個直接臨時應答(比如100 trying)到這個服務端事務上(8.2.6節)。因此,有狀態的proxy不應當給非INVITE請求產生100(trying)應答。
這是一個proxy的模型,並非軟件實現。在實現上可以擴展並且複用用這個模型定義。
對於所有的新請求來說,包括那些未知方法的請求,proxy處理請求必須:
1、    驗證請求(16.3)
2、    預處理路由信息(16.4)
3、    決定請求的目的(targets)(16.5)


+--------------------+
|                        | +---+
|                        | | C |
|                        | | T |
|                        | +---+
+---+    |            Proxy        | +---+ CT = Client Transaction
| S |    |    "Higher" Layer    | | C |
| T |    |                        | | T | ST = Server Transaction
+---+    |                        | +---+
|                        | +---+
|                        | | C |
|                        | | T |
|                        | +---+
+--------------------+
Figure 3: Stateful Proxy Model
4、    轉發請求到每一個目的地(16.6)
5、    處理所有的應答(16.7)
16.3 驗證請求
在proxy轉發請求之前,它必須檢查消息的合法性。一個合法的消息必須經過如下的檢查:
1、    合法的語法
2、    URI scheme
3、    最大轉發次數
4、    (可選)循環檢測(loop detection)
5、    proxy-require
6、    proxy-authorization
如果任何一步失敗了,proxy都必須作爲UAS(8.2)一樣,應答一個錯誤碼。
注意proxy並不要求檢查合併的請求,並且不能把合併的請求當作一個錯誤的情況。終端接收到合併的請求會根據8.2.2.2節講述的內容進行分解。
1、    合法的語法。
請求要能夠被服務端事務處理,那麼請求就必須是語法無誤的。請求中的任何與檢查相關的部分或者與請求轉發節相關的部分都必須語法嚴格無誤。在檢查中,其他部分的嚴格與否,在檢查中都被忽略,並且在轉發消息過程中保持不變。例如,proxy不會由於非法的Date頭域而拒絕請求。同樣的proxy不會在轉發請求的時候移去非法的Date頭域。這個是爲了設計成爲可擴展的。以後的擴展可能定義新的方法、新的頭域。proxy不能拒絕由於包含了不認識的方法或者不認識的頭域而拒絕轉發這個請求。
2、    URI scheme檢查
如果Request-URI包含一個proxy所不能理解的URI形式,那麼proxy應當通過返回一個416來拒絕這個請求(Unsupported URI scheme)。
3、    最大轉發次數檢查
最大轉發次數Max-Forwards頭域(20.22)是用來限制轉發的次數的。如果請求沒有包含Max-Forwards頭域,那麼這個檢查將被忽略。如果請求包含了一個Max-Forwards頭域,並且這個頭域大於0,那麼這個檢查就跳過。如果請求包含一個Max-Forwards頭域,並且這個頭域爲0,那麼這個proxy不能轉發這個請求。如果請求是OPTIONS請求,那麼proxy可以作爲最終響應者來響應這個請求,並且遵循11節講述的產生應答。否則proxy應當返回一個483(too many hops)應答。
4、    可選的Loop Detection檢查
proxy可以檢查看看轉發是否可能導致循環。如果請求包含一個Via頭域,並且這個頭域值,和proxy早先保留的請求的頭域值相同,那麼這個請求就是以前經過過本proxy。要麼這個請求就是循環處理了,要麼就是合法的循環處理。要檢測請求是否循環處理,proxy可以用branch參數,根據16.6節的第8步來計算,並且和接收到的Via頭域做比較。如果參數相等,那麼請求就循環了。如果不等,那麼請求就是合理的經過,並且繼續處理。如果檢測到了循環,那麼proxy應當返回一個482(LoopDetechted)應答。
5、    Proxy-Require檢查
本協議的以後的擴展可能會要求額外的proxy特性。所以終端會在請求中包含一個Proxy-Require頭域來表明會使用到那些特性,這樣proxy就可以根據Proxy-Require判定自己是否能夠支持這些特性。
如果請求包含一個Proxy-Require頭域(20.29)並且有一個或者多個本proxy不能理解的option-tags。那麼這個proxy必須返回一個420(Bad Extension)錯誤,並且這個錯誤應答必須包括一個Unsupported(20.40)頭域列明瞭那些option-tags這個proxy不能支持。
6、    Proxy-Authorization 檢查
如果proxy要求在轉發請求之前進行身份認證,那麼必須根據22.3節中描述的那樣進行請求的檢查。22.3節也定義了proxy應當怎樣處理檢查失敗的情況。
16.4 路由信息預處理
proxy必須檢查請求中的Request-URI部分。如果Request-URI包含了一個本proxy早先放在Record-Route頭域中的值(參見16.6,4),proxy必須用Route頭域中的最後一個值來替換Request-URI,並且從Route頭域中刪去這個值。proxy必須接着按照個修改後的請求進行處理。
這個只會在某元素髮送請求到proxy(proxy本身可能是一個終端),並且這個請求是基於嚴格路由的。在接收到請求後重寫這個字段是必須的,因爲要保持向後兼容性。同時也允許按照本規範實現的元素保護Request-URI通過嚴格路由的proxy(12.2.1.1節)。
這個要求並沒有強制proxy保留狀態來檢查其早先放在Record-Route頭域中的URI。作爲替換的方法,proxy只需要保留足夠的信息在那些URI裏邊,這樣,在以後出現的時候就能識別了。
如果Request-URI包含了maddr參數,proxy必須檢查這個參數來看看是否在proxy配置的可信任的地址列表或者可信任的區域列表中。如果Request-URI包含一個maddr參數,並且這個參數包含了一個proxy可以信任的地址,並且這個請求是通過Request-URI中(指明或者缺省)的端口和協議接收到的,proxy必須抽掉maddr和其他非缺省的端口和通訊參數,並且繼續處理。

proxy可能收到一個帶有匹配maddr的請求,但是不是在URI中指出的端口和transport接收到的。這個請求需要通過指明的port和transport轉發到對應的proxy。

如果Route頭域的第一個值就是這個proxy,那麼proxy必須從請求中把它移去。

16.5 確定請求的目的
接着,proxy計算請求的目的(或者多個目的地)。目的地集合可以由請求的內容決定或者由絕對位置服務提供。目的地集合中的每一個目的地都由URI來表達。

如果請求中的Request-URI包含了maddr參數,必須把Request-URI放在目標集合中,並且是作爲唯一一個目標URI,並且proxy必須按照16.6節中的約定進行處理。

如果Request-URI的區域並非本proxy負責的區域,那麼Request-URI必須放在目標集合中,並且作爲唯一一個目標URI,並且proxy必須按照16.6節中的約定進行處理。

有很多種情況都會導致proxy收到並非本proxy負責的區域的請求。一個防火牆proxy處理外發的請求(就像HTTPproxy處理外發的請求)就是一個典型的例子。

如果請求的目標集合沒有像上邊講述的這樣預先設定,那麼這就意味着proxy是負責Request-URI所指明的區域的,並且proxy可以用任何機制來決定往哪裏發送這個請求。這些機制都可以歸結成爲訪問一個絕對位置服務的形式。這個可能由從SIP註冊服務器創建的位置服務器獲得信息、讀取數據庫、查閱服務器、利用其他協議、或者就簡單的替代Request-URI來實現。當訪問由註冊服務器創建的位置服務的時候,在作爲索引查詢之前,Request-URI必須首先根據10.3節進行規範處理。這些機制的輸出結果將作爲目的地集合。
如果Request-URI沒有提供足夠的信息來讓proxy能夠產生目的地集合,它應當返回一個485(Ambiguous)應答。這個應答應當包含一個Contact頭域包含一些應當嘗試的新位置。比如,一個到sip:[email protected]的INVITE可能在某一個proxy是不明確的,因爲這個proxy有多個JohnSmith。21.4.23節有細節描述。

任何與這個請求有關的,或者與proxy當前環境有關的信息都可以用來構造目的地集合。例如,由於請求的內容不同或者包頭域的不同,可以有不同的目的地集合,又或者不同時間到達的請求也可以有不同的目的地集合,或者不同的時間間隔,上一次失敗的請求,甚至是當前proxy的利用率都可以導致目的地集合的不同。

通過這些機制,我們可以有一個可能的目的地列表,他們的URI被增加到目的地集合。每一個目的地只能在目的地列表中出現一次。如果目的URI已經在這個集合中存在了(基於URI類型的相等定義),那麼它不能再次增加。

如果原請求的Reuqest-URI指明的區域並非本proxy所負責的區域,那麼本proxy不能增加任何額外的目的地到目的地集合。

如果proxy負責Request-URI所指明的區域,那麼這個proxy只可以在轉發的時候改變請求的Request-URI。如果proxy並非負責這個URI,那麼它不會在3xx或者416應答的時候查生遞歸。

如果原始請求的Request-URI是屬於本proxy負責的區域的,那麼proxy可以在請求轉發的時候增加目的地。他可以在處理過程中,用任何可以獲得的信息來決定新的目的地。 例如,proxy可以選擇把一個轉發應答(3xx)所包含的聯繫地址合併到目的地集合中。如果proxy使用一個動態的信息源來構造目的地集合(例如訪問SIP的註冊服務器),它應當在處理請求的過程中監測這個信息源。當有新的目的地出現的時候,就應當加到這個目的地集合裏邊。就像上邊說得,每一個URI只能在集合中出現1次。

只能出現1次的原因是可以降低網絡衝突,在合併重定向請求的聯繫地址的情況下可以防止無限遞歸的出現。

舉例來說,一個簡單的位置服務是一個”no-op”(無操作的),返回的目的URI就是輸入的請求URI。請求將送到一個特定的下一個節點proxy。在16.6節/6小節定義請求轉發中,通過SIP或者SIPS URI表達的,下一個節點的身份, 將在Route的頭域最上一層插入。
如果Request-URI是這個proxy所負責的,但是在本proxy中找不到,那麼proxy必須返回404(Not Found)應答。

如果目的集合經過上邊的處理依舊是空的,那麼proxy必須返回一個錯誤應答,這個錯誤應答應當是408(暫時不可用)。

16.6 請求轉發
當目的地集合不是空的時候,proxy可以開始轉發這個請求。有狀態的proxy可以按照任意的順序處理這個目的地集合。它可以順序處理多個目的地,上一個完成前下一個不能開始。也可以採用並行的處理多個目的地。也可以通過分組的形式,每組之間是串行的,組內是並行的。

通常的處理順序機制是使用一個Contact頭域的qvalue參數來處理(20.10節)。目的地從最高的qvalue開始處理到最低的qvalue。相同qvalue的目的地可以並行處理。

有狀態的proxy必須包含針對目的地集合的一個接收到應答和轉發出去的原始請求進行匹配的機制。爲了完成這樣的目的,這個機制是一個由proxy層在轉發第一個請求前創建的”response context”(應答上下文)來保障的。

對於每一個目的地,proxy轉發請求都遵循下列步驟:
1、    拷貝一個接收到的請求
2、    更新Request-URI
3、    更新Max-Forwards頭域
4、    可選增加一個Record-Route頭域
5、    可選增加附加的頭域
6、    路由信息後處理
7、    決定下一個節點地址、端口、通訊協議。
8、    增加一個Via頭域值
9、    如果需要,增加一個Content-Length頭域
10、    轉發這個新的請求
11、    設置定時器C
下面詳細介紹每一步。

1、    拷貝請求
proxy首先把接收到的請求做一個拷貝。拷貝必須包含接收到的請求的全部頭域。在接下來的處理步驟中未提及的頭域不能刪除。拷貝應當保留接收到的請求的頭域的順序。proxy不能用合併的域名來進行域值的重新排序(參見7.3.1)。proxy不能增加、修改、刪除消息體。
實際上,在實現中並非只是做一個拷貝;首要的事情是爲每一個下一個節點準備一個相同的請求。

2、    Request-URI
在拷貝好的請求中的Request-URI必須用目的地的URI進行替換。如果這個目的URI包含任何在Request-URI中所不能允許的參數,那麼這些參數必須被刪去。
這個步驟是proxy的本質步驟。proxy通過這個機制來把請求轉發到目的地。在某些情況下,接收到的Request-URI會不作更改的添加到目的地集合中。對於這樣的目的地來說,上邊講的替換就等於是沒有任何操作。

3、    Max-Forwards
如果拷貝的頭域包含一個Max-Forwards,proxy必須把這個域值減一。
如果拷貝的頭域沒有包含一個Max-Forwards頭域,proxy必須自己增加一個頭域,缺省值是70。現在有一些UA不會在請求中填寫Max-Forwards頭域。

4、    Record-Route
(假設proxy接收到的這個請求會創建一個對話的情況下),如果希望保留這個請求創建的對話中,後續的請求依舊是要經過本proxy,那麼本proxy必須增加一個Record-Route頭域值在這個拷貝中,並且增加的這個頭域值應當是在其他現存的Record-Route頭域之前。通過請求建立的對話可以包含一個預置的Route頭域。
如果這個請求已經是一個對話的一部分,proxy如果希望以後這個對話的請求依舊經過本proxy,那麼proxy應當增加一個Record-Route頭域值。在12節描述的普通的終端操作中,這些Record-Route頭域值不會對終端使用的路由集合造成任何影響。

如果請求本身已經在對話中的話,如果proxy不增加一個Record-Route頭域在請求的包頭,後續的請求也會經過本proxy。但是,如果當終端中斷並且重新構造這個對話的時候,本proxy就會從對話所經過的節點中刪去。

一個proxy可以在任何請求中增加這個Record-Route頭域值。如果請求並沒有初始化一個對話,終端將會忽略這個頭域值。12節講述了終端如何使用Record-Route頭域來構造Route頭域的。

在請求路徑上的每一個proxy都是獨立的決定是否增加一個Record-Route頭域值的-在請求的Record-Route頭域上的值並不影響這個proxy決定增加還是不增加Record-Route頭域值。

在Record-Route頭域中防止的URI必須是SIP或者SIPS URI。這個URI必須包含一個lr參數(參見19.1.1)。這個URI可以和請求將被轉發的地方不同。這個URI不應當包含通訊參數,除非該proxy確認在後續請求將會經過的下行節點中,都支持這個通訊參數(比如本地網絡等等)。

本proxy提供的這個URI可能會讓其他元素(其他proxy)作出路由決定。本proxy,通常,並不知道其他節點的處理能力,所以,它必須嚴格律己,讓自己遵循規範的SIP實現:SIP URI和TCP或者UDP通訊協議。

在Record-Route中的URI必須指向插入它的元素(或者替代元素),這個意思是說通過附件[4]的服務器定位步驟可以順利找到這個元素,這樣後續的請求才能順利到達同一個SIP元素。如果Request-URI包含一個SIPS URI,或者Route頭域的最上的值(經過後續第6步的處理)包含一個SIPS URI,那麼插入Record-Route頭域的值必須是一個SIPS URI。而且,如果請求不是基於TLS接受的,那麼proxy必須增加一個Record-Route頭域。在相似的情況下,proxy如果從TLS上接收的請求,但是產生的是一個在Record-Route中或者Route頭域最上值中沒有SIPS URI的請求(在第6步後處理之後),必須在Record-Route頭域中增加一個非SIPS URI。

在安全範疇內的proxy必須在對話中保持這個安全範疇。

當Record-Route頭域的URI在應答中又重新到達的時候,如果這個URI值需要重寫的時候,這個URI必須是能夠唯一確定的URI。(就是說,請求可能會經過這個proxy好幾次,造成一個或者多個Record-Route頭域值的增加)。16.7節的第8步提供了一個能夠讓這個URI唯一的一個機制。

這個proxy可以在Record-Route頭域中增加一些參數。這些參數在某些請求的應答中會被反射(echo)回來,比如給INVITE請求的200(OK)應答。通過在消息的參數中保持狀態比在proxy中保持狀態更加有效。

如果proxy想在任何類型的對話中都保持在請求的路徑上(比如在跨越防火牆的對話中),它需要給每一個接收到的請求中,都增加Record-Route頭域,即使是它所不能理解的方法的請求也要增加,因爲這些方法可能是對話相關的,具有對話語義的方法。

在Record-Route頭域中增加的URI只是在當這個請求創建對話的時候有效。舉一個例子,對於一個對話-有狀態的proxy(dialog-stateful proxy),當在對話結束後,如果再收到一個請求,這個請求的Request-URI的值中包含這個URI,那麼它就可以選擇拒絕這個請求。一個對話狀態無關的proxy,當然,沒有對話結束的概念,但是他們可以再這個值中填寫足夠多的信息,這樣就可以在以後的請求來到的時候做對話的ID的比較,並且可以選擇拒絕不匹配這個信息的請求。終端不能在對話外使用這個對話中的Record-Route頭域的URI。參見12節描述的終端使用Record-Route頭域的細節。

當proxy需要查看所有對話中的消息的時候,我們就需要Record-routeing。但是,他會降低處理性能和影響擴展性,因此proxy應當只在特定情況下使用record-route。任何初始化一個對話的SIP請求都可以適用Record-Route。在本文檔中,只有INVITE請求是可以適用的,以後的擴展文檔可能包含其他的方法。

5、    增加附加的頭域
在這一步,proxy可能增加其他適當的頭域。

6、    處理路由信息
proxy可以有一個本地的策略,這個策略要求請求在傳遞到目的地之前,必須經歷一個proxy集合。這樣的proxy必須能夠確保所有的類似的proxy都是鬆路由(loose routers)的。通常,只有當這些proxy都是在相同的區域管理的時候,我們纔可能知道這些proxy是否都是鬆路由的。這個proxy的集合是通過一組URI的集合表示(每一個都包含一個lr參數)。這個集合必須被放置到Route頭域中,並且放置在其他頭域值之前。如果Route頭域不存在,必須增加一個Route頭域,包含這組URI的列表。
如果proxy有一個本地策略要求請求經過一個指定的proxy,在壓棧Route頭域之外的一個方法就是旁路下邊的第10步的邏輯轉發,而是直接發送這個請求到這個指定的proxy的地址,端口,和協議。如果請求有一個Route頭域,這個額外的方法就不能用了,除非它知道下一個節點proxy是一個鬆路由的節點。否則,使用上邊講的增加Route頭域的方法會更有效,更靈活,適應性更好,並且操作更一致。而且,如果Request-URI包含了一個SIPS URI,這個proxy必須用TLS來進行通訊。

如果請求的拷貝中包含了Route頭域,這個proxy必須檢查這個Route頭域的第一個值。如果這個URI並沒有包含lr參數,那麼proxy必須根據下列步驟修改這個請求:

- proxy必須把Request-URI放在Route頭域中的最後一個值。
- proxy必須把第一個Route頭域的值放在Request-URI中,並且從Route頭域中刪去。

把Request-URI添加到Route頭域的最後是爲了讓Request-URI的信息能夠通過嚴格路由的proxy。”Popping”彈掉第一個Route頭域值到Request-URI中是爲了能夠讓嚴格路由元素能夠接收到這個請求(並且用它自己的在Request-URI中的URI和在Route頂部下一個節點的URI)。

7、    確定下一個節點的地址,端口和通訊協議。
proxy可以有自己的策略來決定發送請求到特定的IP地址,端口和transport,可以和Route的值或者Request-URI的值無關。當本proxy不能確定對應ip,端口,transport的服務器是一個鬆路由(loose router)的時候,這樣的策略就不能使用了。但是,除了Route頭域應當像上邊講的這樣使用,我們並不推薦這樣的發送請求的機制。

在沒有這樣一個替代機制的時候,proxy應用附件[4]的步驟來決定應當向哪裏發送這個請求。如果proxy重新規格化請求,並且發送到一個像上邊6點講的嚴格路由的元素,proxy必須應用這些步驟到請求中的Request-URI。否則,如果Route頭域存在,proxy必須應用這些步驟到Route頭域的第一個值;如果Route不存在,proxy必須應用這些步驟到Request-URI。這些步驟最終得到一個序列集合(地址,port,transport)。與使用那個URI作爲附件[4]處理的輸入,如果Request-URI指定了一個SIPS URI,那麼proxy必須把輸入[4]的URI當作是SIPS URI然後遵循[4]的處理步驟進行處理。

就像在附件[4]中講述的,proxy必須嘗試序列集合中的第一組元素,並且依次嘗試序列集合中的每一組元素,直到成功爲止。

對於每一組的嘗試,proxy必須按照這組的通訊要求,對消息進行適當的格式化,並且用一個新的客戶端事務(第8到第10點講述的),進行請求的發送。

由於每一組的發送都是使用心得客戶端事務,這就體現了一個新的分支。因而,第8步插入的Via頭域中的分支參數必須每組發送的都不一樣。

如果客戶端事務報告發送請求失敗,或者它自身的狀態機超時,proxy就應當繼續處理序列集合中的下一組元素。當遍歷完序列集合之後,請求就不能發送到目的地集合。proxy不需要在應答上下文中放什麼應答,然而在別的方面卻需要就像從目的地集合收到一個408(Request Timeout)終結響應一樣的操作。

8、    增加一個Via頭域值
proxy必須在請求的拷貝中增加一個Via頭域值,並且在其他Via頭域值之前增加。這個值的構造可以參見8.1.1.7。這意味着proxy需要計算自己的分支參數,並且應當是全局唯一的分支,並且包含必要的magic cookie。注意這意味着如果請求循環經過本proxy的時候(也就是數次經過同一個proxy),每次的分支參數都不同。

在proxy構造分支參數的值上,有一個附加的約束,用來進行循環的檢測。一個要檢測循環的proxy應當創建一個由兩部分組成的分支參數。第一部分必須滿足8.1.1.7的約束。第二部分是用來做循環檢測的,並且是從螺旋中判定是否存在循環(請求數次經過同一個proxy是正常的,這是螺旋,但是如果是循環,那就不正常了。假定proxy是X,CàX,XàY,YàZ,ZàX,XàA,Aà目的地是正常的,但是如果CàX, XàY, YàZ, ZàX, XàY, 這就是循環了)。

循環檢測是通過這樣的方法檢測的:當請求返回給一個proxy,與處理請求相關的字段並未改變,那麼這個就是循環了。這個分支參數的後一部分應當反應所有的這些頭域(包括所有的Route,Proxy-Require和Proxy-Authorization頭域)。這是確保如果請求從別處重新路由回來,而且這些字段改變了,那麼這就是一個螺旋而不是循環(參見16.3)。通常建立這個比較值的方法是計算一個hash值,通過基於To tag,From tag,Call-ID頭域,收到請求的Request-URI(而不是經過處理過後的Request-URI),Via頭域的最上一個,Cseq頭域的序列號,任何附加的Proxy-Require或者Proxy-Authorization頭域。具體的hash算法是基於實現相關的,但是MD5(RFC1321[35]),用16進製表達,是一個有道理的選擇。(基於64位表達的是不太合適的)。

如果proxy希望檢測循環,那麼”branch”參數必須用包含可能影響處理請求的全部信息構成,包括輸入的Request-URI和其他可能會影響proxy處理路由的字段,通過計算得到。這是檢測循環所必須的,因爲如果請求在路由相關的字段改變以後,重新發回這個服務器,那麼新的處理可能會發送到另外的地方,而不是造成一個循環。

在branch參數的計算上,請求的方法不能計算進去。但是作爲特例,CANCEL和ACK請求(給非2xx應答的)必須和他們對應的請求有相同的branch值。branch參數用於在服務器處理這些請求的時候體現請求之間的相關性(17.2.3和9.2)

9、    如果需要,增加一個Content-Length頭域
如果請求會通過一個基於流的通訊協議發送到下一個節點,並且發送的請求拷貝中沒有包含一個Content-Length頭域,那麼proxy必須增加一個正確的請求包體大小在這個頭域(20.14)。

10、    轉發請求
一個有狀態的proxy必須爲這個請求創建一個新的客戶端事務(根據17.1節描述的那樣),並且指示事務層用第7步指定的地址,端口和協議進行發送。

11、    設定時鐘C
爲了能夠處理INVITE請求沒有產生終結應答的情況,TU使用一個定時器(稱作定時器C)。當INVITE請求被轉發的時候,必須爲客戶端事務設定一個定時器C。這個定時器C必須大於3分鐘。16.7節的2步講述了這個定時器是如何根據臨時應答來更新的,並且16.8節講述了定時器到時的處理步驟。

16.7 應答的處理
當proxy收到一個應答的時候,它首先嚐試定位一個與這個應答匹配的客戶端事務(17.1.3)。如果沒有匹配,proxy必須作爲無狀態的proxy來處理這個應答(即使這個應答是信息性質的應答)。如果與應答匹配的客戶端事務找到了,那麼這個應答將轉給這個客戶端事務進行處理。

將應答轉給對應的客戶端事務(或者更通常的說法是發出請求的或者相關的事務),並不是爲了更強大的處理能力,它是保證了”晚到”的給INVITE請求的2xx應答能夠正確的轉發。

當客戶端事務把應答交給proxy層,將會執行下列步驟:
1、    尋找適當的應答上下文。
2、    用臨時應答來更新定時器C
3、    從最上邊移除Via
4、    在應答上下文中增加應答
5、    檢查這個應答是否需要立刻發送
6、    如果需要,從應答上下文中選擇最好的終結應答。

如果在與這個應答上下文相關的每一個客戶端事務都結束的以後,還是沒有終結應答轉發,那麼proxy必須選擇從已經收到的應答中,選擇轉發”best”應答回去。

下列步驟必須在每一個被轉發的應答上執行。就像每一個請求有超過一個應答被轉發一樣:至少有一個終結應答和0個或者多個臨時應答。

7、    需要合併認證頭域值。
8、    可選的重寫Record-Route頭域值
9、    轉發應答
10、    產生合適的CANCEL請求。

上述每一步在下邊有詳細的描述:

1、    尋找上下文
proxy通過16.6節定義的方法來在尋找轉發原始請求前創建的”應答上下文” 。在這個上下文中進行後續的處理步驟。

2、    爲臨時應答更新定時器C
對於INVITE事務,如果應答是一個返回碼是101到199的臨時應答(就是說,除了100的臨時應答),proxy必須給這個客戶端事務重新設置定時器C。這個定時器可以設置成爲其他的值,和原始值不一樣,但是這個數字必須大於3分鐘。

3、    Via
proxy從應答中移去Via頭域中最上的值。

如果在這個應答中沒有這個Via頭域值,那麼應答的含義就是說這個應答不應當被這個proxy轉發。本節描述的後續處理步驟也不需要繼續處理,而是用8.1.3節定義的UAC處理規則進行處理(傳輸層處理已經進行)。

這種情況可能會發生,比如,當某一個元素產生一個第10節規定的CANCEL請求。

4、    增加應答到上下文
收到的終結應答都會保存在應答的上下文中,直到收到一個由服務端事務產生的針對這個上下文的終結應答爲止。這個應答是從那個服務端事務中收到最佳終結應答的一個候選。即使這個應答不會被選中作爲最佳應答,這個應答的信息也需要用來構造成爲最佳應答。

如果proxy決定嘗試調用3xx應答返回的聯繫地址,並且把他們添加到目的地集合,它必須在把這個應答添加到應答上下文之前把聯繫地址從應答中移除。不過,proxy不應當在源請求的Request-URI是一個SIPS URI的情況下,嘗試調用非SIPS URI。如果proxy嘗試每一個3xx應答給回的聯繫地址,proxy不應當把這個應答添加到應答上下文中。

從應答中刪去聯繫地址的目的是爲了防止下一個節點嘗試本proxy已經嘗試的地址。

3xx應答可能包含SIP,SIPS和非SIP URI。proxy可以自行決定自己調用那些SIP或者SIPS URI,並且把剩下的放在應答上下文中返回。

如果proxy收到一個對於一個Rquest-URI並非SIP URI的請求的416(不支持的URI scheme)應答,但是原始請求的Request-URI是SIP或者SIPS(就是說,proxy在轉發請求的時候自己調換了請求的SIP或者SIPS爲一個什麼其他的東西),proxy應當增加一個新的URI到目的地集合。這個URI應當是剛纔嘗試的非SIP URI的SIP URI版本。對於電話URL來說,這個就是把電話URL的電話號碼部分放在SIP URI的用戶部分,並且設置SIP URI的主機部分成爲當前請求發送者的區域。19.1.6節有電話URL到SIP URI的轉換細節。

在3XX應答的情況下,如果proxy在416上會產生”遞歸”(因爲嘗試SIP或者SIPS URI而導致遞歸),那麼應當在應答上下文中增加這個416應答。

5、    檢查轉發的應答
當終結應答到達服務端事務的時候,下列應答包必須立刻轉發。
- 任何非100(trying)的臨時應答
- 任何2xx應答。
如果收到一個6xx應答,那麼就不立刻進行轉發,如果是有狀態的proxy,那麼還需要cancel所有的依賴於這個事務的客戶端(在10節中描述的那樣),並且不能在上下文中創建新的分支。

這個是和RFC 2543的不同之處,2543要求proxy立刻轉發6xx應答。對於一個INVITE事務來說,如果立刻轉發6xx應答,會使得2xx應答到達別的分支。這個結果就是讓UAC在2xx應答之後收到一個6xx應答,這個是不允許發生的。在新的規則下,基於接收到一個6xx應答,proxy應當產生一個CANCEL請求,那麼這個會給所有等待的客戶端事務一個487應答,這就是6xx應答應當給上行隊列的一個結果。

在服務端事務上發送了終結應答之後,下列的應答應當立刻被髮送:
- 任何給INVITE請求的2xx應答。

一個有狀態的proxy必須不能立刻轉發其他的應答。特別是,一個有狀態的poxy必須不能轉發任何100(Trying)應答。這些應答是作爲後續將被轉發”最佳”應答的候選,通過上邊的”在上下文中增加應答”的步驟增加到應答上下文中。

任何將被立刻發送的應答都必須遵照”7、 需要合併認證頭域值。”和”8、可選的重寫Record-Route頭域值”來處理。

這一步,合併下一步,確保有狀態的proxy能夠精確轉發一個終結應答到一個非-INVITE請求,或者給一個INVITE請求的非2xx應答或者一個或者多個2xx應答。

6、    選擇最佳的應答
對於一個有狀態的proxy來說,如果根據上邊的步驟,沒有任何終結應答被立刻發送,並且在客戶端事務中的所有的客戶端服務都已經終結,那麼這個proxy必須發送一個終結應答到一個應答上下文的服務端事務層。

那麼這個有狀態的proxy就必須從接收到的應答上下文中選擇一個”最佳”的終結應答。如果在上下文中沒有一個終結應答,那麼proxy就必須返回一個408(請求超時)的應答到服務端事務層。

如果應答上下文中有終結應答,那麼proxy就必須從這個應答上下文中取得應答來發送。如果應答上下文中有6xx應答,那麼就必須選擇這個6xx應答。如果沒有6xx應答,那麼proxy應當選擇最小的應答(應答返回代碼比較小)。proxy可以選擇對應最小應答系列中的任意一個應答(比如2xx系列中的任意一個應答)。proxy應當給那些提供對影響請求的應答更多的機會,比如在4xx系列中,選擇401,407,415,420或者484應該稍稍優先一些。

當proxy收到503(Service Unavailable)應答的時候,不應當轉發到上行隊列中,除非它能夠知道這個後續的請求隊列都能產生503的應答。換句話說,就是轉發503就意味着proxy確實不能處理任何請求,不僅僅是Request-URI裏邊的這個地址不能處理請求。如果只有某個應答會產生503,proxy應當產生500應當到上行隊列中。

被轉發的應答都必須遵照”7、 需要合併認證頭域值。”和”8、可選的重寫Record-Route頭域值”來處理。

例如,如果一個proxy轉發一個請求到4個地方,並且收到了503,407,501,和404應答,它可能選擇407(Proxy Authentication Required)應答。

1xx和2xx應答可能和建立對話有關。當請求沒有包含一個To tag,UAC使用在應答中的To tag來區分請求創建的對話的多個應答。如果請求中沒有包含To的tag,那麼proxy必須不能爲1xx或者2xx應答增加這個tag到To頭域。一個proxy不能修改1xx或者2xx應答中的To頭域的tag字段。

在請求的1xx應答中,如果應答沒有To頭域的tag字段的時候,由於proxy不能添加tag字段到這個To頭域,它就不能產生它自己的非100臨時應答。但是它可以把這個請求分支到其他一個UAS上,這個UAS可以和proxy共享同樣的元素。這個UAS可以返回它自己的臨時應答,進入請求創建早期對話中。這個UAS並沒有必要作爲一個proxy的嚴格處理步驟存在。它可以是一個在proxy內部的一個虛擬的UAS實現。

3到6xx的應答是節點到節點傳遞的。當產生了一個3-6系列的應答的時候,每一個節點都作爲UAS一樣,產生它自己的應答,通常基於下行隊列的應答產生自己的應答。對於每一個節點來說,在轉發3到6系列應答回去的時候,如果這個應答沒有包含To tag,那麼這個節點也應當不改變這個to tag。

當收到的應當包含了一個To tag,那麼這個proxy不能夠修改這個To tag。

恩,實際上在proxy轉發3到6系列應答的時候,如果替換了To tag也不會讓上行隊列所經過的節點有影響,保留原始的tag值可以有助於調試。

當proxy需要合併多個應答的信息的時候,從這些應答中選取To tag的方式是任意的,並且產生一個新的To tag可能可以使得調試更加容易。舉例來說,當合並401(Unauthorized)和407(Proxy Authentication Required)信息的時候,或者合併一個未加密的Contact值和未通過驗證的3xx應答的時候,產生一個新的To tag就會讓調試比較容易。

7、    合併認證頭域值
如果選擇的應答是401(Unauthorized)或者407(Proxy Authentication Required),那麼proxy就必須從本應答上下文中的所有其他401(Unauthorized)和407應答中搜集WWWAuthenticate和Proxy-Authenticate 頭域值。並且把這些信息增加到這個應答中。最後的401或者407應答中可能會包含多個WWWAuthenticate和Proxy-Authenticate頭域值。

由於這個請求的一個或者多個目的地可能是需要請求身份驗證的,所以這個蒐集步驟就是必須的。客戶端需要接收到這些所有目的者的應答並且在下一次嘗試的時候,爲每一個目的地提供相關的身份證明。在26節有相關的說明。

8、    Record-Route
如果最終發送的應答中包含Record-Route頭域值,並且是這個proxy所原創提供的值,那麼在發送這個應答前,proxy可能需要重寫這個值。這提供了一個機制,讓proxy能夠給下一個上行節點或者下行節點提供非本機的一個URI地址。這種情況是很常見的,比如,在多地址主機系統就非常有用。

如果proxy是通過TLS收到請求的,並且通過非TLS轉發出去,proxy必須重寫在Record-Route頭域中的URI,重寫成SIPS URI。如果proxy通過非TLS接收到請求,轉發是通過TLS轉發的,那麼proxy必須重寫Record-Route請求頭域的URI爲一個SIP URI。

proxy提供的新的URI必須滿足同樣的Record-Route頭域的URI約束(16.6節的第四步)。並且遵循下列的修改:

URI不應當包含通訊參數除非proxy知道下一個上行(同下行隊列對應的)節點,對於後續的請求都支持相關的通訊參數。

如果proxy打算修改應答中的Record-Route頭域,要做的一件事情就是定位插入的Record-Route頭域值。如果請求是螺旋經過的,並且proxy在每次螺旋的時候都插入了Record-Route值,在應答中(必須在反向路徑中的正確位置)找到正確的需要修改的值就需要一點技巧。上邊的規則強調proxy在增加Record-Route頭域值的時候是必須增加唯一的URI,這樣才能找到正確的一個能夠重寫。我們推薦proxy爲每一個URI的user portion增加一個唯一個proxy實例標誌。

當應答到達的時候,proxy修改第一個和proxy實例標誌匹配的Record-Route。這個修改導致產生一個在user portion部分去掉proxy實例的URI。到下一個循環回來處理的時候,同樣的算法(用參數從上而下的尋找Record-Route頭域值)會更改這個proxy插入的下一個Record-Route頭域值。

對於proxy增加Record-Route頭域值的請求來說,並非每一個應答都包含一個Record-Route頭域。如果應答包含一個Record-Route頭域,那麼就包含這個proxy增加的值。

9、    轉發應答
當”合併認證頭域”和”Record-Route”步驟完成以後,proxy可以對這個應答做其他的附加處理。但是這個proxy不能增加、修改、刪除消息體。並且除非另有指示,除了Via頭域值(在16.7節3步)之外,proxy不能刪除任何頭域值。特別是,proxy不能刪除任何可能增加到與處理和這個應答相關的下一個請求的Via頭域值的”接收到”的參數。proxy必須把應答傳遞到跟這個應答上下文相關的服務端事務。這回導致應答發送到最上的Via頭域值的地方。如果服務端事務不在處理這個發送,這個節點必須作爲無狀態proxy轉發這個應答到服務端通訊層。服務端事務可能已經標誌這個發送應答失敗或者內部狀態機已經設置成爲超時狀態。這些錯誤都應當記錄下來用於診斷錯誤,但是協議並沒有要求proxy做補救措施。

proxy必須維持應答上下文直到所有相關事務都已經終結,甚至在發送完成終結應答後還需要維持。

10、    產生CANCEL請求
如果轉發的應答是一個終結應答,proxy必須給依賴於這個應答上下文的所有客戶端事務,產生CANCEL請求。在收到6xx應答的時候,proxy同樣應當爲所有等待在這個應答上下文的客戶端事務產生CANCEL請求。等待的客戶端事務就是收到了臨時應答,但是沒有收到終結應答(還是出於處理中的狀態),並且沒有任何CANCEL請求與之相關的請求。產生CANCEL請求請參見9.1節。

對於要求基於轉發終結應答而CANCEL的客戶端事務並沒有保證終端不會收到給一個INVITE的多個200(OK)應答。基於多餘一個分支的200(OK)應答可能會在CANCEL請求處理前到達。進一步說,後續的擴展可能會改掉這個產生CANCEL請求的要求。

16.8 處理定時器C
如果定時器C 被出發了,proxy必須要麼用另外一個數值重新設定定時器,要麼終結客戶端事務。如果客戶端事務已經收到了臨時應答,那麼proxy必須產生一個與之匹配的CANCEL請求。如果客戶端事務還沒有收到臨時應答,那麼proxy必須就像收到一個408(Request Timeout)一樣的處理。

允許proxy重設定定時器就意味着允許proxy基於當前條件(比如服務器利用率等等)動態的擴展事務的生命週期。

16.9 處理通訊層的錯誤
如果在轉發請求(參見18.4)的時候,通訊層報告了一個錯誤,那麼proxy必須就像收到了一個503(Service Unavailable)應答一樣的處理。

如果proxy在轉發應答的時候接收到錯誤,那麼他就丟棄應答。proxy不能由於通訊的原因而cancel任何和這個應答上下文相關的客戶端事務。

如果proxycancel了這些客戶端事務,那麼一個惡意的或者出錯的客戶端可以用一個Via頭域導致所有的事務都失敗。

16.10 CANCEL處理
一個有狀態的proxy可以給他自己產生的其他請求在任何時候都產生CANCEL請求(參見9.1遵從接收到對應請求的一個臨時應答)。在接收到一個匹配的CANCEL請求的時候,proxy必須取消任何與應答上下文相關的客戶端事務。

當INVITE請求有一個Expires頭域並且這個頭域值已經超時的情況下,一個有狀態的proxy可以對這個處於pending的INVITE客戶端事務發出CANCEL請求。可是,通常來說,這是不必要的,因爲相關的終端會發出結束事務的信號。

當有狀態的proxy在它自己的服務端事務上處理CANCEL請求的時候,並沒有新的應答上下文會創建。相反,proxy層尋找與這個CANCEL對應請求的現存的應答上下文。如果找到了對應的應答上下文,那麼這個節點應當立刻返回一個200(OK)應答給這個CANCEL請求者。在這個情況下,這個節點就像8.2節定義的UAS一樣的工作。進一步說,這個節點應當爲每一個依賴於這個上下文的客戶端事務產生一個CANCEL請求(就像在16.7節第10步描述的那樣)。

如果一個應答上下文沒有找到,這個節點就無法CANCEL這個請求。它就必須像無狀態proxy一樣轉發這個CANCEL請求(可能這個節點把被CANCEL的請求在先前也當作無狀態的proxy轉發了)。

16.11 無狀態的proxy
當作爲無狀態的時候,proxy就是一個簡單的消息轉發者。很多無狀態的處理步驟和有狀態的時候很類似。不同的地方在下邊描述。

一個無狀態的proxy並沒有事務的概念,或者用於描述有狀態proxy行爲的應答上下文。相反的是,無狀態的proxy處理消息,無論是請求還是應答,都是直接從通訊層處理的(參見18節)。當然,無狀態proxy自己也不重發這些消息。他們只是轉發他們收到的任何重發的消息(他們本身並沒有能力來分辯那些消息是重發的,那些消息是原始消息)。進一步說,當無狀態的處理一個請求的時候,這個節點並不產生它自己的100(Trying)或者其他臨時應答。

無狀態的proxy必須用16.3節描述的那樣來驗證一個請求。

無狀態的proxy必須遵從16.4到16.5節定義的步驟來處理請求,有如下幾點例外:
o 無狀態的proxy必須從目的地集合中,選擇一個並且只能選擇一個目的地。這個選擇必須是根據消息的頭域並且是和服務器時間無關的。特別是,一個重發的請求必須能夠每次都轉發到相同的目的地。進一步說,CANCEL和非路由的ACK請求必須和他們相關的INVITE請求有相同的轉發目的地。

一個無狀態的proxy必須遵循16.6節定義的請求處理步驟,並且有下列的不同:

o無狀態proxy的branchID來說,必須要求在時間上和空間上都是唯一的。也就是說,無狀態的proxy不能簡單的使用一個隨機數產生器來計算branchID的第一個部分(16.6節8步)。這是由於請求的重發需要相同的值,並且無狀態的proxy不能區分重發的請求和原始請求。因此,branch參數的組成部分要求唯一,這樣使得重發的時候能夠填寫相同的值。對於無狀態的proxy來說,branch參數必須作爲一個重發無關的消息處理參數存在。

我們沒有規定無狀態proxy採用何種手段保證branchID的唯一性。不過,下列步驟是推薦的方法。proxy檢查在接收到請求的最上Via頭域值的branchID。如果它是由magic cookie打頭的,那麼branchID的第一個部分就是當作接收到的branchID的hash值。否則branchID的第一個部分就當作是Via頭域的最上值、To頭域的tag、From頭域的tag,Call-ID頭域,Cseq序列號(除了方法部分),接收到的請求的Request-URI的一個hash值。這些頭域值在不同事務中總是不一樣的。

o所有其他的消息轉換(16.6節)必須保證轉發重發的請求的時候能夠轉發到相同的節點。特別是,如果proxy在Record-Route頭域中增加了值,或者在Route頭域中增加了值,proxy必須在轉發重發的請求的時候增加相同的值。至於Via 的branch參數,這就意味着轉發必須是基於時間無關的配置或者請求重發無關的屬性。

o一個無狀態proxy決定轉發的地點是像16.6節10步描述的有狀態的proxy一樣。但是請求是直接交給通訊層發送的,而不是交給客戶端事務。

由於一個無狀態的proxy必須轉發重發的請求到相同的地方,並且增加標誌性的branch參數,它只能用消息中本身的信息和時間無關的配置來計算。如果配置狀態不是時間無關的(比如,如果路由表更新了),這個改變相關的請求,在這個改動開始以後,到在事務超時的時間範圍內,就不能作爲無狀態的轉發了。這個處理這段時間的請求是實現相關的。通常處理的方法,是把這些請求當作事務有狀態的進行轉發。

無狀態的proxy必須不能對CANCEL做特別的處理。CANCEL的處理就像對其他請求的處理一樣進行。特別是,一個無狀態的proxy使用相同的Route頭域來處理CANCEL請求,就像處理其他請求一樣。

對於16.7節中定義的應答處理,對於無狀態proxy來說,並不適用。當一個應答到達一個無狀態proxy,proxy必須檢查最上的Via頭域值的sent-by參數。如果這個地址和這個proxy一樣(就是和proxy插入的先前的請求中的值一樣),那麼這個proxy必須從應答中移除這個頭域值,並且轉發這個應答到下一個Via頭域值。這個proxy必須不能增加,修改或者刪除消息體。除非有特別的說明,proxy必須不能移除其他的頭域值。如果地址不匹配本proxy,消息就必須簡單的悄悄扔掉。

16.12 Proxy Route處理的總結
在沒有本地策略的情況下,proxy對於包含Route頭域的請求處理可以歸結於如下的步驟:

1、    proxy會檢查Request-URI。如果它指向的是本proxy所負責的區域,那麼proxy會用位置服務的結果來替換這個URI。否則,proxy不改變這個URI。
2、    proxy會檢查Route頭域的最上URI。如果這個URI指向這個proxy,這個proxy從Route頭域中移除(這個路由節點已經到達)。
3、    proxy會轉發請求到最上的Route頭域值所標誌的URI,或者Request-URI(如果沒有Route頭域)。proxy通過附件[4]的步驟來產生地址,端口,通訊協議等等用來轉發請求所必須的參數。

如果在請求的路徑中,沒有嚴格路由節點,Request-URI會始終標誌着請求的目的地。

16.12.1例子
16.12.1.1 基本SIP四邊形
本例子是一個基本的SIP四邊傳送,U1->P1->P2->U2,使用proxy來傳送。下邊是過程。

U1 發送:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
發給P1,P1是一個外發的proxy。P1並不管轄domain.com,所以它查找DNS並且發送請求到那裏。它也增加一個Record-Route頭域值:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p1.example.com; lr>

P2收到這個請求。這是domain.com所以它查找位置服務器並且重寫Request-URI。它也增加一個Record-Route頭域值。請求中沒有Route頭域,所以它解析一個新的Request-URI來決定把請求發送到哪裏。
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p2.domain.com; lr>
Record-Route: <sip:p1.example.com; lr>

在u2.domain.com的被叫方接收到這個請求並且返回一個200OK應答:
SIP/2.0 200 OK
Contact: sip: [email protected]
Record-Route: <sip:p2.domain.com;lr>
Record-Route: <sip:p1.example.com;lr>

u2的被叫方並且設置對話的狀態的remote target URI爲:
sip: [email protected]並且它的路由集合是:
(<sip:p2.domain.com;lr>,<sip:p1.example.com;lr>)

這個轉發通過P2到P1到U1。現在U1設置它自己的對話狀態的remote target URI爲:sip:[email protected]並且它的路由集合是:
(<sip:p1.example.com;lr>,<sip:p2.domain.com;lr>)

由於所有的路由集合元素都包含了lr參數,那麼U1構造最後的BYE請求:
BYE sip:[email protected] SIP/2.0
Route: <sip:p1.example.com;lr>,<sip:p2.domain.com;lr>

就像其他所有的節點(包括proxy)會做的那樣,它會使用DNS來解析最上的Route頭域的URI值,這樣來決定往哪裏發送這個請求。這就發到了P1。P1發現Request-URI中標記的URI不是它負責的域,於是它就不改變這個Request-URI。然後看到它是Route頭域的第一個值,於是就從Route頭域中移去,並且轉發這個請求到P2:
BYE sip:[email protected] SIP/2.0
Route: <sip:p2.domain.com;lr>
P2也發現它自己並非負責這個Request-URI的域(P2負責的是domain.com並非u2.domain.com),於是P2並不改變它。它看到自己在Route的第一個值,於是移去這個,並且向u2.domain.com轉發(根據在Request-URI上查找DNS):
BYE sip:[email protected] SIP/2.0

16.12.1.2 穿越一個嚴格路由proxy
在這個例子中,對話建立通過4個proxy,每一個增加Record-Route頭域值。第三個proxy是由嚴格路由實現的(RFC 2543)。
U1->P1->P2->P3->P4->U2

INVITE請求到達U2包括了:
INVITE sip:[email protected] SIP/2.0
Contact: sip:[email protected]
Record-Route: <sip:p4.domain.com;lr>
Record-Route: <sip:p3.middle.com>
Record-Route: <sip:p2.example.com;lr>
Record-Route: <sip:p1.example.com;lr>

並且U2返回了一個200 OK。接着,U2根據第一個Route頭域值發送下邊的BYE請求到P4:
BYE sip:[email protected] SIP/2.0
Route: <sip:p4.domain.com;lr>
Route: <sip:p3.middle.com>
Route: <sip:p2.example.com;lr>
Route: <sip:p1.example.com;lr>

P4並不管轄Request-URI指出的域,於是就不更改這個Reqeust-URI。它發現自己在第一個Route頭域中,於是把自己從Route頭域移除。然後準備發送請求到現在的第一個Route頭域值:sip:p3.middle.com,但是它發現這個URI並沒有包含lr參數,於是在發送前,它把這個請求更改成爲:
BYE sip:p3.middle.com SIP/2.0
Route: <sip:p2.example.com;lr>
Route: <sip:p1.example.com;lr>
Route: <sip:[email protected]>
P3是一個嚴格路由,於是它轉發到P2:
BYE sip:p2.example.com;lr SIP/2.0
Route: <sip:p1.example.com;lr>
Route: <sip:[email protected]>
P2看到Request-URI是它放在Record-Route頭域中的值,於是在進一步處理前,它把這個請求改寫爲:
BYE sip:[email protected] SIP/2.0
Route: <sip:p1.example.com; lr>
P2自己並不管轄u1.example.com,於是它根據Route頭域的值,轉發這個請求到P1。

P1發現自己在Route頭域的最上,於是把自己移除,得到:
BYE sip:[email protected] SIP/2.0
由於P1並不管轄u1.example.com並且沒有其他的Route頭域,P1會基於Request-URI轉發這個請求到u1.example.com。

16.12.1.3 重寫Record-Route頭域值。
在這裏例子中,U1和U2是在不同的私有域空間中,並且他們通過proxy P1開始一個對話,這個P1作爲不同私有namespace的一個網關存在。
U1->P1->U2
U1發送:
INVITE sip:[email protected] SIP/2.0
Contact: <sip:[email protected]>

P1使用自己的定位服務並且發送下邊的信息到U2:
INVITE sip:[email protected] SIP/2.0
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.rightprivatespace.com;lr>

U2發送200 OK應答回給P1:
SIP/2.0 200 OK
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.rightprivatespace.com;lr>

P1重寫它的Record-Route頭域參數,提供成爲U1能夠使用的參數,並且發送給P1:
SIP/2.0 200 OK
Contact: <sip:[email protected]>
Record-Route: <sip:gateway.leftprivatespace.com;lr>

稍後,U1發送接下來的BYE到P1:
BYE sip:[email protected] SIP/2.0
Route: <sip:gateway.leftprivatespace.com;lr>

P1轉發到U2:
BYE sip:[email protected] SIP/2.0

17事務
SIP是一個基於事務處理的協議:部件之間的交互是通過一系列無關的消息交換所完成的。特別是,一個SIP 事務由一個單個請求和這個請求的所有應答組成,這些應答包括了零個或者多個臨時應答以及一個或者多個終結應答。在事務中,當請求是一個INVITE(叫做INVITE事務),當終結應答不是一個2xx應答的時候,事務還包括一個ACK。如果應答是一個2xx應答,那麼ACK並不認爲是事務的一部分。
這個分開的原因是基於傳遞全部200(OK)應答到UAC的INVITE請求的重要性所決定的。要把所有的200應答全部發給UAC,那麼UAS獨自負責這些應答的重新傳送(參見13.3.1.4),UAC肚子負責挨個ACK確認(參見13.2.2.4)。由於ACK的重傳只由UAC發起,所以在自己的事務中進行重傳會比較有效。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章