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
」行為的其他用戶端啟動
將封包傳輸到傳入/傳出封包處理具體而言,客戶希望這些
將套用變更套用至尚未接收的「所有」承載,以及「無」
。
在目前的實作項目中,保證同步行為,原因如下:
- 網路堆疊負責處理所有相關封包。
- 用戶端透過同步 FIDL 呼叫傳送及接收酬載。
- 用戶端透過設定通訊端選項,要求變更封包處理, 也會實作為同步 FIDL 呼叫
將同步 FIDL 呼叫替換為未確認的 I/O 到 zircon 通訊端 傳輸會破壞這些語意。他們產生的問題和解決方法 。
傳送路徑
問題
在「Send Path」中,用戶端只需設定一個
以及通訊端選項setsockopt
是透過同步 FIDL 實作,因此
選項的新值可能套用至在呼叫之前排入佇列的封包
執行。例如:
- 用戶端將
IP_TOS
設為某個值 A,同步更新狀態 。 - 用戶端呼叫
sendto
,後者會將酬載排入 Zircon 通訊端。 - 用戶端將
IP_TOS
設為 B 值。 - 網路堆疊會卸載酬載佇列,並透過
IP_TOS=B
。
解決方案
修改用來設定與傳送封包相關的通訊端選項的 FIDL 處理常式
封鎖路徑,直到網路堆疊的 Zircon 通訊端排除為止
排擠的高爾夫球之後,處理常式會修改相關狀態並
傳回用戶端。因為在呼叫 setsockopt
之前傳送的所有酬載
已從 Zircon 通訊端中移除排入網路堆疊,無
系統會依據新設定處理這些項目
接收路徑
問題
在「Recv Path」中,用戶端可以要求提供
設定通訊端選項,瞭解酬載及其傳遞作業的附加資料。
同樣地,由於 setsockopt
為同步性質,因此有偏移空間。例如:
- 網路堆疊將酬載排入 Zircon 通訊端。
- 用戶端設定
IP_RECVTOS
。 - 用戶端將酬載移除佇列,並在不使用
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 通訊端。我們認為這種做法可以接受 取捨有兩個原因:
- 我們可能支援的控制訊息大小約為 100 個位元組。 這是 <10%,假設 MTU 約 1500 個位元組。
- 絕大多數的控制訊息狀態都是「每個封包」。 網路堆疊會將此物件與每個封包一併保存在記憶體中,並釋放該記憶體 系統必須隨即將封包排入通訊端排入通訊端。因此,系統會 整體記憶體消耗量不會增加
此外,我們將使用 Microbenchmark 來追蹤相關指標, 但只要加入新的控制訊息結果是否建議時,以及結果是否建議 不過,權衡已經沒有價值 我們可以還原為緩慢路徑 (為支援 ICMP 資料圖表,我們必須保留此網址)。
序列化通訊協定
要透過 Zircon 通訊端執行 I/O,最簡單的方法就是定義一個 用於保存 UDP 酬載及其中繼資料的 FIDL 資料表,並使用 FIDL 將其序列化 靜態回應。這種方法的缺點是會強迫 傳送方和接收端,以便將有效負載和中繼資料序列化 緩衝區,保證至少能獲得一份副本。
自向量化通訊端 讀取和寫入 因此建議您建構通訊協定 可以運用向量化 API,避免複製內容
通訊協定
定義 send
和 recv
中繼資料的 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
中的所有欄位都已知,原因如下:
- 用戶端的 ABI 修訂版本會規避所有欄位集合 而在用戶端序列化資料表中。
- 平台會拒絕執行用戶端 不支援用戶端的 ABI 修訂版本。
- 我們一律會透過這個平台建構 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
:
- 代表路由表的已知狀態;所有通訊端和所有通訊端的共用 ID 已撤銷轉送表格的任何變更。
- 代表特定通訊端的已知狀態;因任何變更而失效
這個通訊端可能會變更通訊端行為,例如呼叫
bind
,connect
、setsockopt(..., 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 文件註解之外不需要其他文件。
缺點、替代方案和未知
本提案在使用者空間中建構機械,以此因應他們的動機。 另一種方式是在核心中建構這個機器。
翻譯成核心的草圖:
- 隨著每個
zx::socket
端點,核心都會從SocketAdddres
到max_size
。 - 我們要新增
zx_socket_add_route
/zx_socket_remove_route
系統呼叫 以在對等互連端點上修改對應。 - 我們要新增
zx_socket_send_to
/zx_socket_receive_from
系統呼叫 會使用/提供地址的應用程式
如果呼叫 zx_socket_send_to
的使用者空間含有不在地圖中的地址,
作業將會失敗,而使用者空間需要傳送同步訊息
至 Netstack,要求將該路徑新增至 zx::socket
。如果這樣
要求失敗,則位址作業就會失敗並發生錯誤。
優點
在核心方法中,傳送 UDP 封包 (快速傳送時) 只會傳送單一封包。
syscall (zx_socket_send_to
),而非兩個系統呼叫 (zx_object_wait_many
、
zx_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 類型和系統呼叫之間的緊密耦合,因此 不會自動保持同步。
未來工作
使用事件組合來指出用戶端快取的有效性時,
對 send
和 recv
路徑執行額外系統呼叫。這場系統呼叫
改用 VMO 信號有效性即可排除因此
事件配對會以邏輯方式取代,並用對應至對應 VMO 的 VMO
用戶端的位址空間。之後,用戶端可以使用
簡單唸出 VMO 的部分
不明
還有一個問題是說明如何處理 SendControlData
。也許這樣
您需要 zx_socket_send_to
的額外參數,或者可能
對作業執行任何動作