Yuanchieh's Blog

Yuanchieh's Blog

生命是長期而持續的累積

30 May 2021

深入理解 WebRTC

四年前曾經對於 WebRTC 提供 P2P 影音串流感到新奇,寫下了 WebRTC-介紹與實戰
後來在工作中,開始更深入使用 WebRTC,也開始自架 TURN Server,從 Spec 角度認識了
SDP Spec 閱讀筆記
RFC 5389 - STUN 協定介紹
Coturn Server 架設教學 - on AWS

只是使用上或理解上,始終停留於比較表層的 SDK API 呼叫或是文件閱讀,而沒有真正從大方向理解,決定從 WebRTC For The Curious 深入理解 WebRTC 的原理與細節

本篇文章內容會涵蓋

  1. WebRTC 到底是什麼
  2. WebRTC 的連線由始而終的流程

什麼是 WebRTC

以下內容理解自 WebRTC For The Curious

WebRTC 是 Web Real-Time Communication 縮寫,本身是 API 也是 Protocol,主要是可以讓兩個 WebRTC Agent 建立 即時、雙向、安全的串流,WebRTC API 則是遵守 Protocol 定義所提供的實作,目前多家瀏覽器都有實作 WebRTC API,也有非 Javascript 的 WebRTC API 實作

WebRTC protocol 本身只是集合了多個已知、穩定的協定而提供的完整解決方案,主要包含以下步驟

Signaling:

當兩個 Agent 要互相溝通時,他們不知道彼此的位置、彼此支援的影音版本等等,此時會需要 SDP 描述雙方狀態,並透過管道溝通,SDP 是一個由 Key-Pair 組成的文本協定,只要包含以下幾個項目:

  1. IP/Port (Candidate)
  2. Agent 預期有幾個 Audio/Video Track
  3. Audio/Video codec 支援
  4. 連線資訊 (如 Frag 等)
  5. 安全性資訊 (如 Fingerprint 等)

需注意如何交換 SDP 不再 WebRTC 定義中,可以使用任意協定如 HTTP / Websocket / XMPP 等,只要 Agent 之間能夠交換即可

Connecting

WebRTC Agent 要準備建立連線時,會透過 ICE (Interactive Connectivity Establishment),Agent 本身可能在同個 Local 區網下,也可能在世界的兩端,透過 ICE 找出兩個 Agent 能夠直接連線的方式,這背後隱藏了 NAT Traversal 與 STUN/TURN 細節,會在後續介紹

Security

建立雙向連線後,WebRTC 會在增加連線的安全性,透過 DTLS (Datagram Transport Layer Security) UDP over TLS 與 SRTP (Secure Real-time Transport Protocol)

DTLS 主要用於 Data Channel,WebRTC 透過 DTLS 交握建立安全連線,其中 TLS 驗證部分略與 HTTPS 過程不同,在 WebRTC 中沒有去向 CA 驗證憑證,而是透過先前 Signaling 交換的 fingerprint 檢查

而 Audio/Video 串流則是透過 RTP,為了多疊加一層安全性採用 SRTP 傳輸 RTP 內容,所採用的加密金鑰則是透過 Data Channel 所交換

目前就成功建立雙向、安全的連線後,但是影音串流還必須考量到網路狀況,如封包遺失、頻寬限制等問題

Communicating

採用 SCTP (Stream Control Transmission Protocol) 做傳輸狀況的監控、流量管制,實際上 SCTP 的封包是透過 Data Channel 走 DTLS

全部總結,WebRTC 就是由這些協定所集結而成的

WebRTC API

快速過一下 API 設計

new PeerConnection

建立 WebRTC Session,也就是上述的所有 Protocol 集合

addTrack

建立 RTP Stream,並綁定一個隨機產生的 SSRC,之後在 createOffer 時會帶在 SDP 當中並建立對應的 media session,每當 addTrack 一次都會建立新的 RTP Stream

只要 ICE 後成功建立 SRTP 連線,media packets 就會馬上傳送出去

createDataChannel

如果 SCTP 連線還沒建立則新建立一個,預設只有在某一方要求 data channel 才會建立

當 DTLS 成功建立連線後,SCTP packets 就會開始傳送

createOffer

建立 local SDP

setLocalDescription

先前的 addTrack / createDataChannel 只是暫時性的,只有當呼叫 setLocalDescription 才確定套用修改

通常呼叫 setLocalDescription 就會傳送 offer 讓對方 setRemoteDescription

setRemoteDescription

setRemoteDescription 通知 local agent 對方的 candidate 狀態,如果雙方都 setRemoteDescription就可以開始 connecting

addIceCandidate

通知 WebRTC Agent 增加 remote ICE candidate

ontrack

當 RTP stream 接收到封包時觸發事件,封包應該會吻合 setRemoteDescription 時指定的格式

WebRTC 會透過 SSRC 確認對應的 Media Stream

oniceconnectionstatechange

如果 ICE Agent 有異動會觸發事件,像是遇到網路狀況等

onstatechange

ICE Agent / DTLS Agent 狀態異動時會觸發事件

一. Signaling

WebRTC Agent 一開始建立時並不知道該與誰通訊、也不知道該用什麼格式,所以會需要在初始化時做 Signaling,讓後續的連線有機會發生
WebRTC 並沒有硬性規定 Signaling 傳輸的機制,常見做法是透過 Websocket

什麼是 SDP

WebRTC 仰賴 SDP 交換建立連線所需的資訊,SDP 本身相當好理解,只是要去理解 WebRTC 自定義的參數與代表值

SDP 定義在 RFC4566,每一行代表一個 key / value 組合,一份 SDP 中可以包含多個 Media Description,而一個 Media Description 通常代表一個 Media Stream
例如說一個影片有兩個 Video Stream 外加三個 Audio Stream,那就需要五個 Media Description 描述

如何解讀 SDP

SDP 格式為 k=value,鍵通常是一個英文字母代表,等號後連接值,以下是定義的幾個鍵

  1. v: Version,代表版本
  2. o: Origin,通常代表 id 用以識別
  3. s: Session 名稱
  4. t: Timing,通常是 0 0
  5. m: Media Description
  6. a: Attribute 最常見的鍵
  7. c: Connection,通常是 IN IP4 0.0.0.0

Media Description

一個 Session Description 通常包含多個 Media Description,而 Media Description 則由一連串的 RTP Payload 的格式定義,實際的 codec 會以 rtpmap 表示 如以下

v=0
m=audio 4000 RTP/AVP 111
a=rtpmap:111 OPUS/48000/2
m=video 4000 RTP/AVP 96
a=rtpmap:96 VP8/90000
a=my-sdp-value

這裡定義兩個 Media Description
一個是 audio 帶著 fmt 111,並指定 codec 為 OPUS 另一個是 video 帶著 fmt 96,codec 為 VP8,並帶著第二個屬性值為 my-sdp-value

WebRTC 如何利用 SDP

接著談到 WebRTC 如何使用 SDP 的格式,WebRTC 採用 Offer / Answer 架構,一方 Agent 先主動提供 Offer,另一方再決定是否接受,並回應 Answer 確定 codec 等

Transceivers

Transceiver 指定 Media 傳輸的方向,例如說可以指定「我要用這個格式發送」「我要用這個格式接收而且我不想要收到任何資料」等,主要有四個值

  1. send
  2. recv
  3. sendrecv
  4. inactive
    每產生一個 Transceiver 就會對應有一個 Media Description,並帶有一個 attribute 描述如 a=sendrecv

其餘常見屬性

  1. group:BUNDLE:
    有些 WebRTC Agent 會在一個 connection 上傳輸多種類型的 media stream

  2. fingerprint:sha-256:
    DTLS 要用到的憑證 sha 256 值,用來比對憑證的正確性

  3. setup:
    決定 DTLS Agent 在 ICE 連線建立後的行為

  4. ice-ufrag: ICE Agent 的 user fragment,用來驗證身份

  5. ice-pwd: 同驗證身份使用

  6. rtpmap: 指名 RTP Payload 的 codec

  7. fmtp: Payload 額外屬性質,例如 video encoding 方式等細節

  8. candidate: ICE candidate,供後續 ICE Agent 建立連線使用

  9. ssrc: Media Stream 的 ID

範例 SDP 可以在這邊產生 WebRTC samples Munge SDP

二. Connecting

上一步 Signaling 交換完連線資訊,接著就要建立雙方 P2P 連線,但現實的網路世界在有不同種的 NAT 存在,使得建立連線時會有一些阻礙,為了克服衍生出了 NAT 穿越機制 Interactive Connectivity Establishment (ICE)

NAT 種類

NAT 是在一個區域網路下,由統一的對外路由器共用 public ip,而內部的機器則使用 private ip,如果內部機器需要與外部網路溝通,則透過路由器收發,路由器會記住哪些封包要轉送到對應的內部機器;
不論是因應 IPv4 位置不夠用,又或是安全性考量,NAT 普遍存在於現今的網路環境下

*圖片來自 webrtcforthecurious

而路由器針對封包的發送會有不同的對應產生,假設 Machine A 向 public Host 1 發送請求,則會在路由器註冊一個 Mapping

Endpoint-Independent Mapping

如果 Machine A 要向 public Host 2 發送請求,則可以復用同一個 Mapping

Address Dependent Mapping

如果 Machine A 要向 public Host 2 發送請求,則會產生新的 Mapping

Address and Port Dependent Mapping

如果 Machine A 要向 public Host 1 但是不同的 port 發送請求,則會產生新的 Mapping

針對外部封包的接收會有不同的限制

Endpoint-Independent Filtering

任何 public Host 都可以對同一個 Mapping 送封包給 Machine A

Address Dependent Filtering

只有 Machine A 先送過 public Host 1,則 public Host 1 才可以透過此 Mapping 送封包給 Machine A

Address and Port Dependent Filtering

只有 Machine A 先送過 public Host 1 + port 1,則 public Host 1 + port 1 才可以透過此 Mapping 送封包給 Machine A

小結,可以看到 NAT 限制由上往下越來越嚴謹,一開始任何 public ip 都可以送,接著限制 ip address,最嚴格是要 ip address + port 都吻合才可以送

說完了 NAT 種類,但這些種類會怎麼影響 P2P 連線呢?

STUN

如果今天兩個 webrtc client 都在 NAT 之後,那要建立連線就必須先知道 自己的 public IP,這也是 STUN 協定 所做的事情,讓 client 可以知道 NAT Mapping 對外的 public IP

試想以下情境

  1. client A / client B 都在各自的 NAT 之後,他們想要建立 P2P 連線
  2. client A / client B 此時不知道自己的 public IP
  3. client A / client B 分別向 STUN Server(有固定 public IP) 發出請求,此時 NAT 上會建立 Mapping + client A / client B 取得 public IP 交換
  4. 如果 NAT 是屬於 Endpoint-Independent Mapping + Endpoint-Independent Filtering,則 client A / client B 透過 SDP 交換資訊後,就可以建立雙向連線了

反思如果 NAT 是 Address Dependent Mapping + Address Dependent Filtering,透過 STUN 之後 client A / B 可以直接連線嗎?

答案是不行的喔,因為 client A 建立了與 STUN server 的 NAT Mapping 只能兩者用,client B 想要送封包給 client A 就會被擋下來

TURN

遇到 STUN server 無法用,接著就要用 TURN,TURN server 會居中,所有的封包都透過 TURN server 轉發,但這樣也就不是 P2P 連線了

TURN 的運作機制建立於 STUN 之上,擴充 STUN 封包的格式,流程大概是

  1. client A 建立 Allocation,TURN server 會保留一個 port 給 client A,並回傳 Relayed Transport Address,可以想像成搬進新大樓時一樓信箱有一個格子專屬於你的,所以寄信到這個格子的資料都是要給你的
  2. 當 WebRTC 在交換 SDP 時將 Relayed Transport Address 給對方
  3. 為了避免其他人亂寄資料,client 必須在 Relay Server 幫對方增加 Permission
  4. Allocation / Permission 都有時間限制(LIFETIME參數控制),可以後續 Refresh

ICE

結合以上的連線方法,就組合成 ICE protocol,每個 ICE Agent 會分成 Controlling / Controlled,有一方主導連線過程,按照以下步驟,嘗試找出雙方可以互通的連線

1.Candidate Gathering

Candidate 是指可能被使用的連線方式,共有幾種

  1. host: local interface
  2. srflx: Server Reflexive,也就是從 STUN 那拿到的 public ip
  3. relay: 透過 TURN server 拿到的 ip
  4. prflx: Peer Reflexive,從 peer 那邊拿到自己的 ip,晚點補充

以上資訊收集完後,透過 signaling 方式發送給對方

2.Candidate Selection

因為雙方都蒐集了各自的 candidate,接著就是 full mesh 組合出 Candidate Pair,並按照優先順序開始測試連線,可以成功收發訊息則標記為 Valid Candidate,主導方會挑一個 valid candidate 標記成 Nominated Pair,接著雙方在測試一次是否能成功收發訊息,完成則標記為 Selected Candidate Pair,這一個 session 就用一組連線方式

3. Restart

如果 Selected Candidate Pair 有任何問題,則以上步驟重新來過

最後補充一下 Peer Reflexive,為什麼會有從對方拿到自己所不知道的 ip 這種狀況,主要會發生在 Host Candidate 與 Server Reflexive Candidate 溝通

試想 client A 透過 host candidate 多半拿到是自己的 private ip,此時透過 NAT 向 client B 發送請求,client B 會拿到 NAT 的 public address (也就是 client A 的 Mapping),此時返回 public address 給 client A,此時就是 prflx 其實上述的過程類似於 STUN server 的功用 因為 ICE protocol 是有經過驗證的,所以 client A 可以放心的拿這組 public ip 當作自己的 ip

三. Securing

該怎麼確保收到的 sdp 來自預期的 client / 要如何確保收到的 media 是來正確的 client,WebRTC 結合計有的加密方式 DTLS / SRTP,確保機密性/完整性/身份驗證

DTLS

DTLS 是 TLS 的近親,只是 DTLS 的傳輸層是 UDP 而 TLS 是 TCP; DTLS 是 Client/Server 架構,某一方會先發起通信,接著雙方會交換憑證,而確保憑證的正確性是比對 SDP 中的憑證 hash 值;
完成交握後會取得共同對稱密鑰,後續就用此加密 *圖片來自 webrtcforthecurious

流程基本與 HTTPS 很像,只是 client 也要提供憑證給 server,在 https 這一步是可選的但通常忽略

ServerHello/ClientHello 各自再產生 random secret,拿到憑證後 client 產生 pre-master 並加密傳送給 server,雙方可以得到相同但其他人不知道的 master key

過程中突然想到,既然是 client 最後把 pre-master 加密,那為什麼還要 client/secret random number ? 原因是為了避免回放攻擊,參考 Why does the SSL/TLS handshake have a client and server random? 如果少了 Server random number,那駭客即使不知道內文也沒關係,重複回放請求,Server 沒辦法知道這些請求是過時的 反之如果有 random number,Server 就會發現 master key 不同而中斷請求

SRTP

SRTP 是針對 RTP 設計的加密版本,本身沒有定義產生 key,所以 WebRTC 就拿 DTLS 交握後產生的共同金鑰當作 SRTP 的加密金鑰

具體內容就先略過

Real-time Networking

網路狀況會很大幅度影響即時影音服務的品質,品質則是在 Quality & Latency 中做抉擇,評估網路狀況時會看

  1. Bandwidth: 通道上最大可傳輸的量
  2. Transmission Time and Round Trip Time:從發送到收到回應的時間

在網路狀況不好的情況下,會遇到幾種負面情況

  1. Jitter: RTT 時長時短,導致畫面卡頓
    可以透過 Jitter Buffer 改善問題,收到封包後先 queue 一小段時間,等到可以完整解析 frame 在 render
  2. Packet Loss: 某些封包掉了,導致畫面跳格
    解決 Packet Loss 問題可以透過
  • 收到封包後回傳 ACK
  • 收到封包後回傳收到的封包 SACK
  • 回傳沒有收到的封包 NACK (遇到跳號時)

今天如果網路狀況不好,則發送方應該要降低影音品質 / 減緩發送速度,避免網路狀況惡化;
另一方面當網路狀況改善,則發送方也應該要知道,進而提升傳輸的品質與速度

那接收方如何回報網路狀況給發送方呢?

Media

WebRTC 本身不管底層的影音 codec,只要雙方都可以處理就好,透過 RTP 傳輸影音資料 / RTCP 處理網路狀況+控制流量

RTCP 如何整理網路狀況

RTCP 透過幾種方式,讓發送方可以得知網路狀況

  1. Receiver Reports
    Sender 再發送時打上當時的時間戳記 t1,Receiver 收到後加上自己處理的時間 t2,回傳 Report 給 Sender,最後 Sender 收到的時間為 t3
    則 Sender 可以算出 rtt = t3 - t1 - t2
  2. REMB
    Reciver 根據統計 packet loss,跟 Sender 回報預期的 bitrate
if (packetLoss < 2%) video_bitrate *= 1.08
if (packetLoss > 10%) video_bitrate *= (1 - 0.5*lossRate)

但這個方法理論上看起來不錯,實際應用上卻很糟糕,因為 encoder 在動態改變 bitrate 下沒有這麼有效率 3. TWCC
追蹤每一個封包的狀況,可以讓 Sender 有更即時、清楚的網路狀況

這一段理解不這麼全面,未來有機會補上

總結

建立 WebRTC 連線過程,總共分成

  1. Signaling: 交換彼此資訊的管道,透過 SDP 描述這些資訊
  2. Connecting:為了應付複雜的網路環境(NAT),透過 ICE 解決連線問題
  3. Securing:確保交換的資料是安全的
  4. Real-time communication:

Categories