RFC-0109:快速 UDP 通訊端

RFC-0109:快速 UDP 通訊端
狀態已接受
領域
  • 網路堆疊
說明

使用 Zircon 通訊端實作網路 UDP 通訊端資料傳輸。

毛皮變化
作者
審查人員
提交日期 (年-月-日)2021-06-17
審查日期 (年-月-日)2021-06-25

摘要

使用 Zircon 通訊端實作網路 Datagram 通訊端資料傳輸機制。 透過用戶端快取網路狀態實作用戶端引數驗證。

提振精神

提高 Datagram 通訊端處理量並降低 CPU 使用率。

https://fxbug.dev/42094952 Datagram 網路通訊端導入之前 使用 zircon 通訊端;用戶端會zx_socket_write傳送資料 需 zx_socket_read 才能接收資料。我們透過最低限度的通訊協定來傳遞中繼資料 例如目的地地址 (如果呼叫提供 應用程式。

此方法已捨棄,因為此做法並未提供信號錯誤, 在特定情況下呼叫呼叫端假設應用程式想 使用酬載傳送至網路堆疊沒有路徑的遠端;順序 以符合目標為 Linux 的第三方軟體 實作必須傳回錯誤,以指出未採取任何行動 代表來電者。

目前的實作是以 fuchsia.posix.socket/DatagramSocket.{Recv,Send}Msg。 傳送和接收資料都需要多個情境切換,因為 支援 FIDL 回應。正在忽略 FIDL 序列化:

+-----------------------------+-------------------------+
| Client                      | Netstack                |
+-----------------------------+-------------------------+
| `zx_channel_call(...)`      |                         |
|                             |                         |
|                             | `zx_channel_read(...)`  |
|                             | `zx_channel_write(...)` |
|                             |                         |
| (`zx_channel_call` returns) |                         |
+-----------------------------+-------------------------+

反轉 (接收資料) 看起來則大同小異。比較對象 未確認的 I/O:

+-------------------------+------------------------+
| Client                  | Netstack               |
+-------------------------+------------------------+
| `zx_channel_write(...)` |                        |
+-------------------------+------------------------+
| `zx_channel_write(...)` | `zx_channel_read(...)` |
+-------------------------+------------------------+

雖然可以使用 Zircon 頻道和 FIDL 未經確認的 I/O 作業,但卻無法 這可能導致記憶體增加。因此,我們建議 如要使用 Zircon socket

+------------------------+-----------------------+
| Client                 | Netstack              |
+------------------------+-----------------------+
| `zx_socket_write(...)` |                       |
+------------------------+-----------------------+
| `zx_socket_write(...)` | `zx_socket_read(...)` |
+------------------------+-----------------------+

設計

中繼資料驗證

使用未確認的 I/O 會預先宣告 Datagram 通訊端中繼資料 (例如 並能在本機機器上完整驗證,不必另外設定 驗證結果 透過與通訊端的多次互動快取資料 (因此驗證費用 都可以分期

請注意,這項假設並不會保留 IPPROTO_ICMP 通訊端, 檢查其酬載是否有效,所以現行的 FIDL 通訊協定 並用於「效能不重要」或「深度」的地方 需要驗證。

從現有類型擷取 FIDL 以重複使用,並重新命名 DatagramSocket 清晰度:

protocol BaseDatagramSocket {
  compose BaseSocket;

  /// Retrieves creation information from the socket.
  GetInfo() -> (Domain domain, DatagramSocketProtocol proto) error fuchsia.posix.Errno;
}

protocol SynchronousDatagramSocket {
  compose BaseDatagramSocket;

  /// Receives a message from the socket.
  RecvMsg(bool want_addr, uint32 data_len, bool want_control, RecvMsgFlags flags) -> (fuchsia.net.SocketAddress? addr, bytes data, RecvControlData control, uint32 truncated) error fuchsia.posix.Errno;
  /// Sends a message on the socket.
  SendMsg(fuchsia.net.SocketAddress? addr, bytes:MAX data, SendControlData control, SendMsgFlags flags) -> (int64 len) error fuchsia.posix.Errno;
}

使用驗證函式定義新的 FIDL 通訊協定:

/// Matches the definition in //zircon/system/public/zircon/types.h.
const uint32 ZX_WAIT_MANY_MAX_ITEMS = 64;

/// Describes an intent to send data.
type SendMsgArguments = table {
  /// The destination address.
  ///
  /// If absent, interpreted as the method receiver's connected address and
  /// causes the connected address to be returned in [`SendMsgBoardingPass.to`].
  ///
  /// Required if the method receiver is not connected.
  1: to fuchsia.net.SocketAddress;
}

/// Describes a granted approval to send data.
type SendMsgBoardingPass = resource table {
  /// The validated destination address.
  ///
  /// Present only in response to an unset
  /// [`SendMsgArguments.to`].
  1: to fuchsia.net.SocketAddress;
  /// Represents the validity of this structure.
  ///
  /// The structure is invalid if any of the elements' peer is closed.
  /// Datagrams sent to the associated destination after invalidation will be
  /// silently dropped.
  2: validity vector<zx.handle:<EVENTPAIR, zx.RIGHTS_BASIC>>:ZX_WAIT_MANY_MAX_ITEMS;
  /// The maximum datagram size that can be sent.
  ///
  /// Datagrams exceeding this will be silently dropped.
  3: maximum_size uint32;
}

protocol DatagramSocket {
  compose BaseDatagramSocket;

  /// Validates that data can be sent.
  ///
  /// + request `args` the requested disposition of data to be sent.
  /// - response `pass` the constraints sent data must satisfy.
  /// * error the error code indicating the reason for validation failure.
  SendMsgPreflight(SendMsgArguments args) -> (SendMsgBoardingPass pass) error fuchsia.posix.Errno;
};

定義要在 Zircon 通訊端傳送的 FIDL 結構:

/// A datagram received on a network socket, along with its metadata.
type RecvMsgPayload = table {
  1: from fuchsia.net.SocketAddress;
  2: control RecvControlData;
  3: datagram bytes:MAX;
};

/// A datagram to be sent on a network socket, along with its metadata.
type SendMsgPayload = table {
  1: args SendMsgArguments;
  2: control SendControlData;
  3: datagram bytes:MAX;
};

用戶端嘗試傳送資料的方式如下: (水平排列) 圖表:

+--------------------------------+               +---------------------------+               +----------------+
| cache.getSendMsgBoardingPass() | - Present ->  | checkPeerClosed(validity) |   +- ZX_OK -> | Return success |
+--------------------------------+               +---------------------------+   |           +----------------+
 |    ^                                            ^                     |  |    |
 |    |                                            |                     |  |  +------------------------------+
 |  +------+                  +----------------------------------+       |  |  | socket.write(SendMsgPayload) | - != ZX_OK -----+
 |  | Send |    +- Success -> | cache.storeSendMsgBoardingPass() |       |  |  +------------------------------+                 |
 |  +------+    |             +----------------------------------+       |  |                         ^                         |
 |            +--------------------+                                     |  |                         |                         |
 +- Absent -> | SendMsgPreflight() |  +- (ZX_OK, ZX_SIGNAL_PEER_CLOSED) -+  +- (ZX_ERR_TIMED_OUT) -+  |                         |
              +--------------------+  |                                                            |  +- No -+                  |
                |                ^    |   +-----------------------------------+                    |         |                  |
                |                |    +-> | cache.removeSendMsgBoardingPass() |                    |   +---------------------+  |
                |                |        +-----------------------------------+                    +-> | size > maximum_size |  |
                |                |          |                                                          +---------------------+  |
                |                |          |  +--------------+                                              |                  |
                |                +----------+  |              | <-------------------------------------- Yes -+                  |
                |                              | Return error |                                                                 |
                +- Failure ------------------> |              | <---------------------------------------------------------------+
                                               +--------------+

用戶端 cache「實作」的位置SendMsgPreflight;這大概是地圖 從 fuchsia.net.SocketAddress(vector<zx::eventpair>, maximum_size)

請注意,這項策略結構的快取最終會具有一致性是 用戶端的有效性檢查和 酬載可以使用 Datagram 通訊端 盡可能提供最適當的放送語意

保證同步行為

背景

在 POSIX 語意下,客戶會預期系統提供同步 send 和「recv」行為的其他用戶端啟動 將封包傳輸到傳入/傳出封包處理具體而言,客戶希望這些 將套用變更套用至尚未接收的「所有」承載,以及「無」 。

在目前的實作項目中,保證同步行為,原因如下:

  1. 網路堆疊負責處理所有相關封包。
  2. 用戶端透過同步 FIDL 呼叫傳送及接收酬載。
  3. 用戶端透過設定通訊端選項,要求變更封包處理, 也會實作為同步 FIDL 呼叫

將同步 FIDL 呼叫替換為未確認的 I/O 到 zircon 通訊端 傳輸會破壞這些語意。他們產生的問題和解決方法 。

傳送路徑

問題

在「Send Path」中,用戶端只需設定一個 以及通訊端選項setsockopt 是透過同步 FIDL 實作,因此 選項的新值可能套用至在呼叫之前排入佇列的封包 執行。例如:

  1. 用戶端將 IP_TOS 設為某個值 A,同步更新狀態 。
  2. 用戶端呼叫 sendto,後者會將酬載排入 Zircon 通訊端。
  3. 用戶端將 IP_TOS 設為 B 值。
  4. 網路堆疊會卸載酬載佇列,並透過 IP_TOS=B
解決方案

修改用來設定與傳送封包相關的通訊端選項的 FIDL 處理常式 封鎖路徑,直到網路堆疊的 Zircon 通訊端排除為止 排擠的高爾夫球之後,處理常式會修改相關狀態並 傳回用戶端。因為在呼叫 setsockopt 之前傳送的所有酬載 已從 Zircon 通訊端中移除排入網路堆疊,無 系統會依據新設定處理這些項目

接收路徑

問題

在「Recv Path」中,用戶端可以要求提供 設定通訊端選項,瞭解酬載及其傳遞作業的附加資料。 同樣地,由於 setsockopt 為同步性質,因此有偏移空間。例如:

  1. 網路堆疊將酬載排入 Zircon 通訊端。
  2. 用戶端設定 IP_RECVTOS
  3. 用戶端將酬載移除佇列,並在不使用 IP_TOS 的情況下將其傳回使用者 控管訊息
解決方案

在網路堆疊中,將每個酬載排入佇列,同時加入執行程式碼所需的最低狀態 導出任何支援的控制訊息。

請定義 FIDL 方法,以擷取目前要求的控制訊息組合:

protocol DatagramSocket {
  compose BaseDatagramSocket;

  /// Returns the set of requested control messages.
  ///
  /// - response `cmsg_set` the set of currently requested control messages.
  RecvMsgPostflight() -> (struct {
    cmsg_set @generated_name("RequestedCmsgSet") table {
      /// Represents the validity of this structure.
      ///
      /// The structure is invalid if the peer is closed.
      1: validity zx.handle:<EVENTPAIR, zx.RIGHTS_BASIC>;
      /// Identifies whether the `IP_RECVTOS` control message is requested.
      2: ip_recvtos bool;
    }
  });
};

在用戶端中,快取目前要求的控制訊息組合,並使用 設定篩選器,可篩選網路堆疊針對 依照下列程序操作:

+-----------------------------------------+
| socket.read() -> (Payload, RecvMsgMeta) | -----------> ZX_OK
+-----------------------------------------+                |
  |                                                        |
  |                    +-----------------------------+     |
  |                    | cache.getRequestedCmsgSet() | <---+
  |                    +-----------------------------+
  |                               |    |
  |                               |    |
  |                               |    |
  |  Absent  <--------------------+    +-----------------------------> Present
  |    |                                                                   |
  |    |  +-----------------------+                                        |
  |    |  | Return Payload, cmsgs |     +---------------------------+      |
  |    |  +-----------------------+     | checkPeerClosed(validity) |<-----+
  |    |           ^                    +---------------------------+
  |    |           |                      |          |         ^
  |    |           |                      |          |         |
  |    |   (ZX_ERR_TIMED_OUT) <-----------+          |         |
  |    |                                             |         |
  |    |                                             |         |
  |    |     +--(ZX_OK, ZX_SIGNAL_PEER_CLOSED) <-----+         |
  |    |     |                                                 |
  |    |     |                                                 |
  |    |     |      +--------------------------------+         |
  |    |     +--->  | cache.removeRequestedCmsgSet() |         |
  |    |            +--------------------------------+         |
  |    |                                 |                     |
  |    |                                 |                     |
  |    |    +---------------------+      |                     |
  |    +--> | RecvMsgPostflight() |  <---+                     |
  |         +---------------------+                            |
  |                     |      |                               |
  |                     |      |                +------------------------------+
  |  +-------Failure <--+      +--> Success --> | cache.storeRequestedCmsgSet()|
  |  |                                          +------------------------------+
  |  |
  |  |
  |  |    +--------------+
  |  +--> |              |
  |       | Return error |
  +-----> |              |
          +--------------+

為每個酬載新增控制訊息狀態,會在複製時增加負擔 移入和移出 Zircon 通訊端。我們認為這種做法可以接受 取捨有兩個原因:

  1. 我們可能支援的控制訊息大小約為 100 個位元組。 這是 <10%,假設 MTU 約 1500 個位元組。
  2. 絕大多數的控制訊息狀態都是「每個封包」。 網路堆疊會將此物件與每個封包一併保存在記憶體中,並釋放該記憶體 系統必須隨即將封包排入通訊端排入通訊端。因此,系統會 整體記憶體消耗量不會增加

此外,我們將使用 Microbenchmark 來追蹤相關指標, 但只要加入新的控制訊息結果是否建議時,以及結果是否建議 不過,權衡已經沒有價值 我們可以還原為緩慢路徑 (為支援 ICMP 資料圖表,我們必須保留此網址)。

序列化通訊協定

要透過 Zircon 通訊端執行 I/O,最簡單的方法就是定義一個 用於保存 UDP 酬載及其中繼資料的 FIDL 資料表,並使用 FIDL 將其序列化 靜態回應。這種方法的缺點是會強迫 傳送方和接收端,以便將有效負載和中繼資料序列化 緩衝區,保證至少能獲得一份副本。

向量化通訊端 讀取和寫入 因此建議您建構通訊協定 可以運用向量化 API,避免複製內容

通訊協定

定義 sendrecv 中繼資料的 FIDL 資料表:

/// Metadata used when receiving a datagram payload.
type RecvMsgMeta = table {
  1: fuchsia.net.SocketAddress from;
  2: RecvControlData control;
};

/// Metadata used when sending a datagram payload.
type SendMsgMeta = table {
  1: SendMsgArguments args;
  2: SendControlData control;
};

fuchsia.io/NodeInfo 中傳回至用戶端, Describe,指定用來接收包含以下內容的位元組的緩衝區大小 序列化中繼資料資料表:

type NodeInfo = strict resource union {
  // Other variants omitted.
  9: datagram_socket resource struct {
    /// See [`fuchsia.posix.socket.DatagramSocket`] for details.
    socket zx.handle:<SOCKET, zx.rights.TRANSFER | zx.RIGHTS_IO | zx.rights.WAIT | zx.rights.INSPECT>;
    /// Size of the buffer used to receive Tx metadata.
    tx_meta_buf_size uint64;
    /// Size of the buffer used to receive Rx metadata.
    rx_meta_buf_size uint64;
  };
};

讓我們:

tx_meta_bytes = fidl_at_rest::serialize(SendMsgMeta)
tx_meta_size = len(tx_meta_bytes)

傳送酬載時,用戶端會建構並排入下列緩衝區:

      ( 2 )       (tx_meta_size)   (tx_meta_buf_size - tx_meta_size)
+--------------+-----------------+----------------------------------+---------+
| tx_meta_size |  tx_meta_bytes  |             Padding              | Payload |
+--------------+-----------------+----------------------------------+---------+

網路堆疊:

  • 2 + tx_meta_buf_size + max_payload_size 分配有空間的緩衝區 並將訊息排入該緩衝區中。
  • 將前兩個位元組解讀為 uint16,用於識別 保留序列化中繼資料的緩衝區區域,以位元組為單位。
  • 使用 FIDL 靜態反序列化 SendMsgMeta

接收路徑是以完全對稱的方式運作。

uint32_t tx_meta_buf_size = fidl::MaxSizeInChannel<fuchsia_posix_socket::wire::SendMsgMeta, fidl::MessageDirection::kSending>();

這會計算傳送方向中訊息大小的邊界,假設 表示沒有未知的欄位網路堆疊可以安全地假設 但 RecvMsgMeta 中的欄位是已知的,因為網路堆疊本身會將該欄位序列化 撰寫新的電子郵件訊息系統可假設 SendMsgMeta 中的所有欄位都已知,原因如下:

  1. 用戶端的 ABI 修訂版本會規避所有欄位集合 而在用戶端序列化資料表中。
  2. 平台會拒絕執行用戶端 不支援用戶端的 ABI 修訂版本。
  3. 我們一律會透過這個平台建構 Netstack。這項假設 完全依賴現有系統在這裡 。

實作

將新的實作內容新增至 fuchsia.io/NodeInfo

resource union NodeInfo {
    /// The connection composes [`fuchsia.posix.socket/DatagramSocket`].
    N: DatagramSocket datagram_socket;
};

/// A [`NodeInfo`] variant.
// TODO(https://fxbug.dev/42154392): replace with an anonymous struct inline.
resource struct DatagramSocket {
    zx.handle:<SOCKET, zx.RIGHTS_BASIC | zx.RIGHTS_IO> socket;
};

變更傳回類型 fuchsia.posix.socket/Provider.DatagramSocket敬上 轉換為變化版本:

/// Contains a datagram socket implementation.
resource union DatagramSocketImpl {
    1: DatagramSocket datagram_socket;
    2: SynchronousDatagramSocket synchronous_datagram_socket;
}

...並變更行為,讓每次情況發生時,系統都會傳回 DatagramSocket (即呼叫端未要求 ICMP 通訊端)。

初始實作應為每個項目提供兩個元素 SendMsgBoardingPass.validity:

  1. 代表路由表的已知狀態;所有通訊端和所有通訊端的共用 ID 已撤銷轉送表格的任何變更。
  2. 代表特定通訊端的已知狀態;因任何變更而失效 這個通訊端可能會變更通訊端行為,例如呼叫 bindconnectsetsockopt(..., SO_BROADCAST, ...)setsockopt(..., SO_BINDTODEVICE, ...)

成效

SOCK_DGRAM 通訊端的輸送量預計為兩倍以上;本 預估值是依據 https://fxbug.dev/42094952.

CPU 使用率預計會減少一個有意義但未知規模。

人體工學

這項異動不會對下游使用者造成實質影響 不會直接耗用這裡提供的介面

回溯相容性

在一開始離開時保留 ABI 相容性 fuchsia.posix.socket/Provider.DatagramSocket敬上 未變更,並以 DatagramSocket2 的形式實作新功能。 完成必要的 ABI 轉換後,請將 DatagramSocket2 重新命名為 DatagramSocket,並移除先前的實作。追蹤其他 ABI 轉換,移除 DatagramSocket2

安全性考量

此提案不會影響安全性。

隱私權注意事項

此提案對隱私權沒有任何影響。

測試

現有的單元測試涵蓋了受影響的功能。

說明文件

且此處除了 FIDL 文件註解之外不需要其他文件。

缺點、替代方案和未知

本提案在使用者空間中建構機械,以此因應他們的動機。 另一種方式是在核心中建構這個機器。

翻譯成核心的草圖:

  1. 隨著每個 zx::socket 端點,核心都會從 SocketAdddresmax_size
  2. 我們要新增 zx_socket_add_route / zx_socket_remove_route 系統呼叫 以在對等互連端點上修改對應。
  3. 我們要新增 zx_socket_send_to / zx_socket_receive_from 系統呼叫 會使用/提供地址的應用程式

如果呼叫 zx_socket_send_to 的使用者空間含有不在地圖中的地址, 作業將會失敗,而使用者空間需要傳送同步訊息 至 Netstack,要求將該路徑新增至 zx::socket。如果這樣 要求失敗,則位址作業就會失敗並發生錯誤。

優點

在核心方法中,傳送 UDP 封包 (快速傳送時) 只會傳送單一封包。 syscall (zx_socket_send_to),而非兩個系統呼叫 (zx_object_wait_manyzx_socket_write)。

這個結果可能不盡理想,因為 體現使用者族群的做法發現我們總是zx_object_wait_many time::infinite_past,我們可以對作業進行最佳化,使其在沒有 系統呼叫,前提是必須使用不可分割狀態進行維護 作業。這項操作可能需要 vDSO 中的處理常式資料表。 否則就可能造成這種情況

對有執行階段的用戶端來說,使用 zx_object_wait_async 的替代方案 而不是 wait_many 維護本機快取,因此能加快 避免發出額外的系統呼叫

我們也會避免對 FIDL 靜態的依附元件 FIDL 中的額外資料複製功能會複製到 系統呼叫,可將酬載直接複製到最終目的地。

缺點

在核心方法中,沒有明顯的 O(1) 路徑方法 轉送表格變更時,系統會取消轉送。如先前所述,我們可以 傳送至 zx_socket_remove_route,移除所有路徑 (或許需要 就這樣,但網路堆疊必須 zx_socket_remove_route 每個通訊端。

我們能很厲害,讓 zx_socket_add_route 能參加活動 但那次的巴洛克已成了漂亮的巴洛克文

將這些結構烘焙到核心中,要引進 另一個則是對平台進行的 通訊協定演變模型現在 特定 FIDL 類型和系統呼叫之間的緊密耦合,因此 不會自動保持同步。

未來工作

使用事件組合來指出用戶端快取的有效性時, 對 sendrecv 路徑執行額外系統呼叫。這場系統呼叫 改用 VMO 信號有效性即可排除因此 事件配對會以邏輯方式取代,並用對應至對應 VMO 的 VMO 用戶端的位址空間。之後,用戶端可以使用 簡單唸出 VMO 的部分

不明

還有一個問題是說明如何處理 SendControlData。也許這樣 您需要 zx_socket_send_to 的額外參數,或者可能 對作業執行任何動作