Signal如何使用Rust構建大規模端到端加密視訊通話?

banq發表於2021-12-16

Signal 在一年前釋出了端到端加密群組通話,從那時起,我們從支援 5 個參與者一直擴充套件到 40 個。沒有現成的軟體可以讓我們同時支援這種規模的通話確保所有通訊都是端到端加密的,因此我們構建了自己的開源訊號呼叫服務來完成這項工作。這篇文章將更詳細地描述它的工作原理。
 

選擇性轉發單元 (SFU)
在群組通話中,每一方都需要將他們的音訊和影片傳送給通話中的所有其他參與者。有 3 種可能的通用架構可以這樣做:

  • 全網狀:每個呼叫參與者將其媒體(音訊和影片)直接發​​送給其他呼叫參與者。這適用於非常小的呼叫,但不適用於許多參與者。大多數人的 Internet 連線速度不夠快,無法同時傳送 40 個影片副本。
  • 伺服器混合:每個呼叫參與者將其媒體傳送到伺服器。伺服器將媒體“混合”在一起並將其傳送給每個參與者。這適用於許多參與者,但與端到端加密不相容,因為它要求伺服器能夠檢視和更改媒體。
  • 選擇性轉發:每個參與者將其媒體傳送到伺服器。伺服器將媒體“轉發”給其他參與者而不檢視或更改它。這適用於許多參與者,並且與端到端加密相容。

由於 Signal 必須具有端到端加密並擴充套件到許多參與者,因此我們使用選擇性轉發。執行選擇性轉發的伺服器通常稱為選擇性轉發單元或 SFU。
 SFU 中程式碼主迴圈的簡化版本如下所示:

let socket = std::net::UdpSocket::bind(config.server_addr);  
let mut clients = ...;  // changes over time as clients join and leave
loop {
  let mut incoming_buffer = [0u8; 1500];
  let (incoming_size, sender_addr) = socket.recv_from(&mut incoming_buffer);
  let incoming_packet = &incoming_buffer[..incoming_size];

  for receiver in &clients {
     // Don't send to yourself
     if sender_addr != receiver.addr {
       // Rewriting the packet is needed for reasons we'll describe later.
       let outgoing_packet = rewrite_packet(incoming_packet, receiver);
       socket.send_to(&outgoing_packet, receiver.addr);
     }
  }
}



為了擴充套件到更多參與者,我們用 Rust 從頭開始​​編寫了一個新的 SFU。它現在已經為所有 Signal 組呼叫服務了 9 個月,輕鬆擴充套件到 40 個參與者(未來可能更多),並且具有足夠的可讀性,可以作為基於 WebRTC 協議(ICESRTP運輸-cc, 和googcc )。
現在讓我們更深入地瞭解 SFU 中最難的部分。您可能已經猜到了,它比上面的簡化迴圈更復雜。

SFU 中最難的部分
SFU 最困難的部分是在網路條件不斷變化的同時將正確的影片解析度轉發給每個呼叫參與者。
這個困難是以下基本問題的組合:

  1. 每個參與者的 Internet 連線容量都在不斷變化並且很難知道。如果 SFU 傳送過多,則會造成額外的延遲。如果 SFU 傳送的太少,質量就會很低。因此,SFU 必須不斷仔細地調整它傳送給每個參與者的數量,以使其“恰到好處”。
  2. SFU 不能修改它轉發的媒體;要調整它傳送的數量,它必須從傳送給它的媒體中進行選擇。如果可供選擇的“選單”僅限於傳送可用的最高解析度或根本不傳送,則很難適應各種網路條件。因此,每個參與者必須向 SFU 傳送多種解析度的影片,並且 SFU 必須不斷小心地在它們之間切換。

解決方案是結合幾種我們將單獨討論的技術:
  • 聯播和資料包重寫允許在不同的影片解析度之間切換。
  • 擁塞控制確定要傳送的正確數量。
  • 速率分配決定在該預算內傳送什麼。

詳細點選標題原文

 

相關文章