WebRTC技術協議系列-ICE初始offer接收詳解

​在前面的章節中,筆者具體討論了關於發送初始offer的細節。這裡,我們將討論接收初始offer的一些具體內容。關於接收初始offer的流程主要包括幾個部分的討論:驗證ICE支持,決定主控/被控方角色,採集候選地址,候選地址優先級排序,選擇默認的候選地址,SDP解碼, 檢查列表的構建和定時檢查。其中,在接收offer的內容中,候選地址採集,候選地址排序,選擇默認候選地址和SDP解碼和前面關於發送offer的流程非常相似,因此,在本章節中,這些內容的介紹可能相對比較簡潔,筆者將花費更多時間在驗證ICE,決定接收角色,檢查列表構建和定時檢查的討論中。下面,我們將根據agent接收offer的處理步驟,開始討論這些具體的處理流程。

特別聲明,以下圖例均來自互聯網資源。


驗證ICE支持能力

Agent收到初始offer以後,首先,它需要驗證ICE的支持能力。對於每個在SDP中收到的媒體流來說,如果媒體流的每個構件的默認目的地地址出現在了候選地址屬性中,agent將會根據RFC5245規範中ICE的處理流程來進行處理。如果我們進一步做具體理解的話,我們可以參考RTP的處理方式。例如,使用RTP時,在c行和m=行的IP地址和端口出現在了候選地址屬性參數中;使用RTCP時,RTCP值也會出現在候選地址屬性中。如果前面所說的這個條件不成立,或者這些媒體構件沒有出現在候選地址屬性中的話,agent必須按照另外一個流程來處理SDP(RFC3264)值無需按照ICE的處理機制來處理。這個流程就是我們在前面文章中所介紹的關於SIP針對offer/answer交互模式來處理。讀者可以查看歷史文檔來回顧筆者介紹的相關基礎內容。如果按照RFC3264處理的話,讀者也需要注意幾個例外條件:

  1. 所有agent必須遵從RFC5245-10中的會話存活保持流程。
  2. 如果agent沒有根據RFC5245 ICE的處理流程處理的話,是因為有a=候選地址屬性,但是這些a=候選屬性沒有匹配媒體流的默認目的地地址,agent在其answer消息中必須包括一個a=icemismatch屬性(無匹配)。
  3. 如果默認候選地址是一個從TURN服務器學習獲得的轉發地址,agent必須在TRUN服務器端創建一個授權許可,這個許可支持SDP中收到的,從對端的peer學習到的IP地址。大家需要注意,如果權限設置有問題的話,對端發過來的初始數據包將會丟失。


決定主控/被控制方角色

在上一篇文章中,筆者介紹了主控agent和被控方agent的角色。在會話中,每個agent需要充當一個角色來執行不同的操作流程。主控方負責選擇最終候選配對和被控方進行通信,如果是全部署環境下的agent,這表示ICE使用挑選的候選配對對每個媒體流進行傳輸,並且,當agent需要時,可基於ICE的選擇生成更新的offer消息。如果是輕量級的部署環境中的agnet,選定為主控方agent表示選擇了候選地址配對,這個候選配對是基於offer和answer消息中的配對(在IPv4中僅有一對),並且,當agent需要時,生成一個更新的offer消息來反映這個選擇。被控方agent被告知使用的候選配對來傳輸媒體流,並且針對這個單個信息不會生成更新offer。下面,筆者將討論決定雙方角色的規則和處理流程對雙方的的影響。關於決定雙方角色的規則和其流程的影響事實上取決於雙方agent所處的部署環境,這裡有三種不同的部署環境需要討論:雙方都在全部署場景,雙方各自在全部署場景/輕量級部署場景,雙方都在輕量級部署場景。


雙方都在全部署場景中,如果一個agent生成了offer消息,並且啟動了ICE處理流程,這個agent必須扮演一個主控方agent的角色。另外一側agent則必須扮演被控方角色。雙方agent將會構建檢查列表,運行ICE狀態機和連接檢查的流程。主控方將會根據全部署場景流程執行處理邏輯,挑選候選配,ICE根據這些候選配對提供進一步的選擇,最後雙方agent通過更新offer來更新或者結束ICE處理流程。當然,在實際部署場景中,不排除一些特殊使用環境,例如因為其他通信因素,雙方的角色認定發生衝突。雙方agnet錯誤地認為自己是主控方或者自己是被控方。因此,為了避免類似情況的發送,每個agent必須選擇一個任意號碼,RFC5245規範稱之為tie-breaker,在連接檢查中使用此數值檢測修復這種情況,其取值範圍一律發佈在0和(2**64) - 1之間(它是一個64位的正整數)。


雙方一方是全部署場景agent,另外一方是輕量級部署場景agent中,全部署場景的agent必須扮演主控方agent的角色,輕量級agent則必須扮演被控方agent的角色。全部署agent將會構建檢查列表,運行ICE狀態機和生成連接檢查。主控方將會根據全部署場景流程執行處理邏輯,挑選候選配,ICE根據這些候選配對提供進一步的選擇。輕量級agent將會監聽連接檢查,接收響應檢查消息,按照輕量級部署的結束ICE處理流程,最後對ICE進行結束處理。因為雙方的角色不同,從某種程度來說,主控角色一般都是一直處於運行狀態。因此,對輕量級部署agent來說,針對每個媒體流來說,ICE處理狀態被認為是運行狀態,所有ICE流程也是運行狀態。


Agent雙方都是輕量級部署的agent的話,如果一個agent生成了offer消息,並且啟動了ICE處理流程,這個agent必須扮演一個主控方agent的角色。另外一側agent則必須扮演被控方角色。這種環境中,雙方從來都不會發送連接檢查。準確地說,一旦雙方的offer/answer交互模式完成以後,每個agent將執行ICE結束處理流程,它們無需經過連接檢查的流程。和第一種場景中所描述的一樣,同樣的角色衝突問題也可能出現在雙方都是輕量級部署agent的環境中。它們都可能認為自己是主控方agent或者被控方agent。這種情況下的處理方式和全場景部署中的角色決定的處理方式不同。輕量級部署環境中角色衝突時,雙方agent通過在信令中承載的offer/answer交互,交互消息中所支持的檢測能力來確定雙方角色。對輕量級部署agent來說,針對每個媒體流來說,ICE處理狀態被認為是運行狀態,所有ICE流程也是運行狀態。


在會話中,一旦雙方角色確定以後,除非ICE出現啟動,否則,它們將一直持續充當各自的角色。補充說明,因為ICE重新啟動以後,雙方需要重新決定各自的角色,如果是全部署場景agent的話,它們需要重新對tie-breaker賦值計算。


候選地址採集

關於針對候選地址的採集處理流程,answerer應答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經非常詳細地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內容。根據RFC5245的推薦,提供方收到offer,早於對用戶提醒之前馬上執行採集流程。當agent啟動時,這樣的候選地址採集方式就可能開始。


候選地址優先級排序

關於針對候選地址的排序處理流程,answerer應答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經非常詳細地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內容。


默認候選地址選擇

關於針對候選地址的默認候選地址選擇的處理流程,answerer應答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中已經非常詳細地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內容。


SDP解碼

關於針對SDP解碼的處理流程,answerer應答方和offerer提供方的處理方式是完全一樣的。筆者在前面的文章中針對全部署場景和輕量級部署場景的規定已經非常詳細地做出了說明。用戶可以閱讀此文章來了解全部署場景中的場景流程和輕量級部署的要求等內容。


檢查列表構建

構建檢查列表是由全部署場景來實現的,如果是輕量級的部署場景,無需構建檢查列表。因為offer/answer交互模式的使用,在媒體使用的過程中需要一個檢查列表。為了對媒體流構建一個檢查列表,agent需要經過幾個必要的步驟來構建檢查列表,agent需要經過的步驟是:構建候選地址配對,計算候選配對優先級和排序,優選配對,最後計算配對狀態。接下來,筆者將分別討論這四個主要的步驟。


首先,為了實現對媒體流的支持,agent選擇自己每個候選地址和從對端peer收到的候選地址進行配對處理。這裡,本地候選地址稱之為LOCAL CANDIDATES,遠端的候選地址稱之為REMOTE CANDIDATES。選擇過程中,agent同時還要考慮安全的問題,為了防止選擇的地址被攻擊,agent可以設置從offer或answer中接收候選地址的數量。關於STUN被攻擊的可能性討論,請讀者參考RFC5245-18,後期文章中我們將討論這個話題,現在不做討論。如果本地候選地址和遠端候選地址配對成功的話,它們必須具有相同的component ID,和同樣的IP地址版本。當然,實際環境中,本地候選地址和遠端候選地址也完全可能存在不匹配或者匹配不成功的可能,或者,遠端候選地址和本地候選地址匹配不成功的可能。有時也可能發生這樣的問題,例如,針對一個媒體流來說,agent沒有包含候選地址來支持此媒體流的所有構件模塊。如果是這樣的情況的話,此媒體流的構件數量就會受到影響而減少,並且會認為這個數量等於雙方agent所要求的構件最低數量,此最低數量值針對媒體流的所有component構件,並且來源於雙方agent所提供的最大component ID(關於ID取值範圍讀者可參考前面的介紹和歷史文檔)。


除了上面所介紹的一些情況以外,還有幾個比較特殊的情況和讀者說明。在涉及到RTP/RTCP的使用場景中有可能發生這樣的情況,一方agent提供了RTCP的候選地址,但是對端可能沒有提供類似地址。有時,offer消息提供方可在同一端口多路複用RTP/RTCP數據,並且通過SDP屬性中指示了這樣的實現方式(多路複用RTP/RTCP參考RFC5761-5和RFC8035)。但是,如果answerer執行了多路複用RTP/RTCP的話,offerer則不知道answerer執行了這樣的流程,offerer就會按照默認的設置方式使用各自獨立的RTP和RTCP,這樣處理的結果就會導致每個媒體流中在offer消息中包含兩個component。answerer方執行了多路複用RTP/RTCP,對每個候選地址來說,它將僅包含單個component。因此,此component將是RTP/RTCP mux的合併值。如果此候選地址只有單個component的話,ICE結束執行候選地址配對。毫無疑問,如果配對中本地候選地址和遠端候選地址都是默認候選地址時,這個候選配對被認為是默認的候選配對。如果agent雙方不是ICE感知的agent的話,媒體流構件使用此默認候選配對傳輸媒體流。讀者可以參考以下示例來理解check list(檢查列表)核心概念和與其他模塊之間的關係。

WebRTC技術協議系列-ICE初始offer接收詳解

check list列表和其他模塊之間的關係彙總


構建候選地址配對完成以後,需要進行後續地址配對的優先級計算。優先級計算是根據以下格式來計算的。其中,G表示主控方agent提供的候選優先級,D表示被控制方提供的候選地址優先級。這裡,G>D?1:0是一個表達式,如果G大於D,則取值為1,否則為0。


<code>pair priority = 2 ^ 32 *MIN(G,D) + 2 *MAX(G,D) + (G > D?1:0)/<code>


關於以上優先級的計算,很多開源項目有類似的處理方式,讀者可以參考一些開源項目來做進一步瞭解。以下一段代碼是一個配對計算源代碼示例,讀者可以參考:


// PairPriority computes Pair Priority as in RFC 8445 func PairPriority(controlling, controlled int) int64 {

var (

g = int64(controlling)

d=int64(controlled) )

// pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0) v := (1

<<32)*min(g, d) + 2*max(g, d)

if g > d {

v++ }

return v }

一旦優先級被設定以後,agent就會按照順序對候選地址配對進行排序。排序的規則按照優先級順序遞減的方式進行。如果兩組候選地址配對有相同的優先級,它們兩組配對的實現可以任意排序。


獲得了候選地址配對排序以後,此優選候選地址會生成一個排序後的配對列表。ICE將會按照排序列表逐一進行連接檢查。在每個檢查流程將會涉及發送請求的流程,agent需要從本地候選地址發送檢查請求到遠端候選地址。這裡注意,因為agent不能直接從反射地址發送請求,它僅能從base基準地址發送請求,所以agent需要通過排序後的配對列表來發送請求。對每個配對來說,如果本地候選地址是一個反射地址的話,base基準地址必須替換這個反射候選地址。一旦替換流程完成後,agent必須過濾或篩選此列表。過濾配對列表的流程通過移除配對的方式來實現。具體來說,如果它(需要移除的配對)的本地候選地址/遠端候選地址和優先級列表中較高優先級的一對配對相同的話,則需要移除配對相同的較低優先級的配對。


另外,為了安全的考慮,防止STUN服務器被攻擊,agent必須限制連接檢查數量,在一定的數值設置環境中,agent的檢查將會覆蓋所有連接列表的地址,這個特定數值必須是可配置的數值。規範RFC5245推薦默認數值是100。候選配對列表一直保持低於100的限定設置,一些低優先級的候選配對將被強制丟棄。在可能的情況下,RFC5245推薦儘量使用比較低的設置限定,在實際生產環境中也可能看一些用戶針對配對檢查設置了最大檢查限定。要求支持可配置的限定設置也是為系統提供了一個工具,如果發現問題以後,可以通過此限定值來排查問題。


完成了候選配對地址的挑選以後,檢查列表需要進行狀態計算。每個候選配對支持了一個foundation和一個state(狀態)。實際上,這裡的foundation是合併了本地候選地址的foundation和遠端的候選地址的fundation而生成的一個新的fundation。一旦開始計算foundation時,每個配對將會設定一個狀態值,根據檢查結果的不同,候選配對需要經過五個可能或潛在的狀態計算(歷史文章也有介紹這五個計算步驟)。


WebRTC技術協議系列-ICE初始offer接收詳解

候選配對狀態計算


ICE開始運行時,一個候選配對將遷移到以下任何一種狀態。筆者再次重複一次每個狀態的任務然後介紹具體流程處理:

  • Frozen:處於鎖定狀態,等待檢查
  • Waiting:檢查還沒有啟動,等待從check list中選擇最高優先級的候選配對進行處理
  • In-Progress:為這個配對發送check請求,事務在處理流程中
  • Succeeded:配對檢查成功,生成成功結果
  • Failed:配對檢查失敗,既沒有生成任何響應也沒有生成任何還原響應


在檢查列表中的每個候選配對的初始狀態需要經過一個狀態計算。其狀態計算需要按續經過以下幾個步驟:

  1. agent將會把每個檢查列表中所有的候選配對設置為Frozen 封凍狀態。
  2. agent將會為第一個媒體流在檢查列表中檢查(第一個媒體流出現在SDP offer和answer中的第一個m行中)。然後針對此媒體流做狀態設定處理。對所有具有同樣foundation的配對,agent將會設置一個帶比較低級別component ID的配對狀態,要求這些配對進入等待狀態。如果有多個類似這樣的配對,agent則首先使用具有最高優先級的配對。


這裡有兩個特定的列表稱謂需要讀者注意。其中檢查列表中的一部分配對會進入等待狀態,另外一部分則進入到封凍狀態。如果檢查列表中至少有一對配對是在等待狀態的,這樣的列表稱之為活動檢查列表。如果檢查列表中所有配對都是封凍狀態的,這樣的列表稱之為封凍檢查列表


除了檢查列表中的配對檢查有狀態存在,檢查列表自己本身也關聯一個狀態,針對正在工作的媒體流,這個狀態用來捕捉ICE檢查的狀態。檢查列表具有三種狀態:

  1. 運行狀態:針對正在運行的媒體流,ICE檢查狀態也在運行中。
  2. 完成狀態:在這個狀態下,ICE檢查已經生成了一個經過挑選的配對支持媒體流構件模塊。接下來,ICE成功完成處理任務,媒體流開始被髮送。
  3. 失敗狀態:在這個狀態下,針對此媒體流的支持,ICE檢查還沒有成功。


作為一個offer/answer交互的結果,檢查列表首先構建後會被ICE遷移到運行狀態中。ICE處理流程覆蓋所有的媒體流過程中,因此,ICE也和這個流程本身有一個關聯綁定的狀態。當ICE運行時,這個狀態等於運行狀態。當ICE處理流程完成後,這個狀態將是完成狀態,如果ICE處理流程失敗的話,這個狀態就是失敗狀態。關於以上幾個狀態之間切換的規則筆者將在定時檢查的討論中做更多說明。


定時檢查


前面我們一直在講關於check的流程,check是由全部署場景生成的,所以,如果agent是輕量級的部署方式的話,可以跳過此內容討論。Agent執行兩種check,一種是ordinary checks,另外一種是triggered checks。兩種check都是由一個定時器來控制,針對媒體流數據,定時器會週期性地觸發check生成事件。agent也會維護一個先進先出的隊列,這個隊列稱之為triggered check隊列,這個隊列維護候選配對,如果有check的機會的話,這個隊列將會發送配對進行檢查。當定時器觸發以後,agent將會從triggered checks隊列頂部移除一個配對,對這個配對執行連接檢查,最後把這對候選配對的狀態設置為正在處理的狀態(In-Progress)。如果在triggered checks隊列中沒有配對的話,agent將會發送ordinary checks。


一旦完成構建檢測列表計算的流程,agent將會針對每個active check list設置一個定時器。每Ta*N秒觸發一次定時器,這裡的N是active check list的數量(初始階段,至少有一個active check list)。Ta和RTO這兩個定時器會在未來的討論中加以介紹,這裡不再展開討論。Ta乘以N將允許整個check的吞吐量分裂到所有active check list中。不同的在實際部署環境中,這個定時器觸發的頻率可以低於上面的設置,同時,也應該考慮定時器傳播的問題,儘量不要同時對每個媒體發佈定時器。第一個定時器馬上觸發後,agent在這一瞬間(offer/answer交互模式已完成)執行連接檢查,然後在Ta秒後執行下一個檢查(因為在第一個定時器觸發時只有一個active check list)。


如果定時器觸發以後,agent沒有triggered checks需要發送的話,它必須按照以下規則選擇一個ordinary checks:

  1. 找到在等待狀態的check list中最高優先級的配對(注意以下兩種狀態下配對處理流程)。
  2. 如果有這樣的配對的話:首先,從本地候選配對發送一個STUN check請求到遠端候選配對。此STUN check請求將會進行相關處理。關於STUN check請求的處理流程,筆者在後續文章中介紹。然後對候選配對的狀態進行設置,設置其狀態為In-Progress狀態。
  3. 如果沒有這樣的配對的話:agent需要找到在封凍狀態的check list中最高優先級的配對。如果有這樣的配對的話,對此配對解除封凍狀態,然後對此配對執行check流程,使其狀態切換為In-Progress狀態。如果沒有這樣的配對的話,結束針對此check list的定時器。


配對流程完成以後,需要保證其檢查數據的完整性。如果要計算check消息的完整性,agent需要使用遠端用戶名稱和密碼來完成計算。遠端用戶名稱和密碼是agent學習遠端peer發送的SDP中的用戶名稱和密碼獲得。


在接下來的章節中,筆者將繼續分享初始應答接收的話題(驗證ICE支持,決定角色。。。)。


參考資料:

https://www.rfc-editor.org/rfc/rfc5245

https://www.rfc-editor.org/rfc/rfc8445

https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/state

https://github.com/gortc/ice/blob/master/pair.go


分享到:


相關文章: