Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec layer 0 — Module io_uring, Stage 2b: network operations

Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS)

Position. Stage 2b specifies the 12 network operations of io_uring in 6.12 (master doc io-uring-0-inventaire-en.md, axis B). They reuse the core from Stage 1 and the conventions of Stage 2a (references to families, buffer model S1, typed offsets/sentinels). This Stage is strategic for AirCom (ADR-001): this is where zero-copy (send_zc, sendmsg_zc) for the data plane and FD passing (capabilities) via sendmsg/recvmsg control messages live.


1. Cross-cutting conventions for Stage 2b

  • Types shared with family-net (ADR-022 D2): SocketAddr, SocketDomain, SocketType, MessageFlags, ShutdownMode, AcceptFlags, SendMessageRequest, ReceiveMessageRequest, ControlMessages. See family-net.md for their definitions and equivalent syscall numbers.
  • MSG_NOSIGNAL by default on all sends: no SIGPIPE on a closed socket (the error surfaces as EPIPE in the completion). This is an Air invariant, explicitly overridable.
  • SOCK_CLOEXEC by default on created FDs (accept, socket): consistent with the net family and the other layer 0 families.
  • Owned addresses: a SocketAddr to be transmitted is serialized into an owned sockaddr storage, moved into slot S1 (the kernel reads it asynchronously — it must survive until completion).
  • Direct descriptor variants (result stored in a registered FD table rather than an ordinary FD) and multishot: deferred to Stage 3a and Stage 3d respectively. bind/listen exist precisely to operate on these direct FDs without an ordinary FD.

2. Connection establishment

2.1 submit_accept

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn submit_accept(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
        -> Result<SubmissionToken, Errno>;
    pub fn submit_accept_with_peer(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
        -> Result<SubmissionToken, Errno>;
}
}
  • Opcode: IORING_OP_ACCEPT (13). Equivalent: accept4.
  • Completion: completion.accepted_fd() -> Result<OwnedFd, Errno> (CLOEXEC by default). The _with_peer variant captures the peer address in an owned storage in the slot ⇒ completion.into_accept_result() -> (OwnedFd, SocketAddr).
  • Multishot: submit_accept_multishot at Stage 3d (a single SQE accepts continuously). Direct fd: _direct variant at Stage 3a.
  • Errors: EAGAIN (nothing to accept, non-blocking FD), EMFILE/ENFILE, ECONNABORTED, EINVAL.

2.2 submit_connect

#![allow(unused)]
fn main() {
pub fn submit_connect(&mut self, sock: BorrowedFd<'_>, address: SocketAddr)
    -> Result<SubmissionToken, Errno>;
}
  • Opcode: IORING_OP_CONNECT (16). Equivalent: connect.
  • Completion: completed(). Errors: ECONNREFUSED, ETIMEDOUT, EINPROGRESS (should not surface in io_uring: the op completes at the end), EALREADY, EISCONN.

2.3 submit_socket

#![allow(unused)]
fn main() {
pub fn submit_socket(&mut self, domain: SocketDomain, ty: SocketType, protocol: i32)
    -> Result<SubmissionToken, Errno>;
}
  • Opcode: IORING_OP_SOCKET (45). Equivalent: socket.
  • Completion: completion.into_socket_fd() -> Result<OwnedFd, Errno> (CLOEXEC by default). Direct fd: _direct variant at Stage 3a.
  • Motivation: create a socket inside the ring, chainable (Stage 3c) with connect/bind without a synchronous round-trip.

2.4 submit_bind / submit_listen

#![allow(unused)]
fn main() {
pub fn submit_bind(&mut self, sock: BorrowedFd<'_>, address: SocketAddr)
    -> Result<SubmissionToken, Errno>;                       // OP 56
pub fn submit_listen(&mut self, sock: BorrowedFd<'_>, backlog: u32)
    -> Result<SubmissionToken, Errno>;                       // OP 57
}
  • Opcodes: IORING_OP_BIND (56) / IORING_OP_LISTEN (57) — added in 6.11. Equivalents: bind / listen.
  • Motivation: allow bind/listen on a socket created as a direct descriptor (submit_socket_direct), which has no ordinary FD usable by the classic bind(2)/listen(2) calls.
  • Completion: completed(). Errors: EADDRINUSE, EACCES, EINVAL.

3. Data transfer (one-shot)

3.1 submit_send / submit_receive

#![allow(unused)]
fn main() {
pub fn submit_send(&mut self, sock: BorrowedFd<'_>, buffer: Vec<u8>, flags: MessageFlags)
    -> Result<SubmissionToken, Errno>;                       // OP 26
pub fn submit_receive(&mut self, sock: BorrowedFd<'_>, buffer: Vec<u8>, flags: MessageFlags)
    -> Result<SubmissionToken, Errno>;                       // OP 27
}
  • Equivalents: send / recv. MSG_NOSIGNAL added by default to send.
  • Buffer: ownership transfer (S1). Completion: into_buffer_result() -> (Vec<u8>, usize).
  • recv: completion.socket_has_pending_data() (CQE_F_SOCK_NONEMPTY) indicates there is more data to read — useful for deciding whether to issue another recv.
  • Multishot recv and provided buffers (BUFFER_SELECT): Stage 3d/3b.
  • Errors: ECONNRESET, EPIPE (send on closed socket, thanks to NOSIGNAL), EAGAIN, EMSGSIZE.

3.2 submit_send_message / submit_receive_message

#![allow(unused)]
fn main() {
pub fn submit_send_message(&mut self, sock: BorrowedFd<'_>, request: SendMessageRequest)
    -> Result<SubmissionToken, Errno>;                       // OP 9
pub fn submit_receive_message(&mut self, sock: BorrowedFd<'_>, request: ReceiveMessageRequest)
    -> Result<SubmissionToken, Errno>;                       // OP 10
}
  • Equivalents: sendmsg / recvmsg.
  • SendMessageRequest (owned, moved into the slot) aggregates: data buffers (Vec<Vec<u8>>), an optional destination address, and control messages (ControlMessages) — including SCM_RIGHTS for FD passing. This is the mechanism by which AirCom transports a capability (an unforgeable FD) between two peers (ADR-001). The façade builds the struct msghdr + cmsg internally, stored in the slot.
  • ReceiveMessageRequest (owned): receive buffers + a control buffer pre-sized for incoming cmsgs (received FDs). Completion: completion.into_receive_message_result() -> Result<(ReceiveMessageRequest, ReceiveMessageMeta), Errno> where ReceiveMessageMeta (backed by io_uring_recvmsg_out) provides namelen, controllen, payloadlen, flags (including MSG_TRUNC/MSG_CTRUNC to check). Received FDs are extracted from the cmsgs as OwnedFd (CLOEXEC).
  • FD passing safety: received FDs are materialized as OwnedFd; a truncated cmsg (MSG_CTRUNC) is reported and partial FDs are closed cleanly (no FD leak).
  • Errors: same as send/recv + EINVAL (malformed cmsg on the send side).

4. Zero-copy (AirCom data plane)

4.1 Two-completion lifecycle — the key point

send_zc/sendmsg_zc avoid the kernel copy of the payload: the kernel references the userspace pages directly. This has a major impact on ownership:

A zero-copy submission produces two completions:

  1. Result completion — carries the number of bytes sent (res ≥ 0) and the flag CQE_F_MORE (a notification will follow).
  2. Notification completion — carries CQE_F_NOTIF; it signals that the kernel no longer references the buffer, which may now be reused or freed.

The buffer must remain alive until the NOTIF completion, not just until the result. The façade manages this via slot S1:

  • the slot retains the buffer until CQE_F_NOTIF is received;
  • the result completion (F_MORE) exposes the byte count via into_result() without returning the buffer;
  • the NOTIF completion (is_notif()) returns the buffer via into_zero_copy_buffer() and releases the slot.

This is the only case in the module where a slot outlives its first completion; it is specified explicitly here and tested (result→NOTIF sequencing, NOTIF without a preceding result on early failure).

4.2 submit_send_zero_copy / submit_send_message_zero_copy

#![allow(unused)]
fn main() {
pub fn submit_send_zero_copy(&mut self, sock: BorrowedFd<'_>, buffer: Vec<u8>, flags: MessageFlags, zero_copy: ZeroCopyFlags)
    -> Result<SubmissionToken, Errno>;                       // OP 47
pub fn submit_send_message_zero_copy(&mut self, sock: BorrowedFd<'_>, request: SendMessageRequest, zero_copy: ZeroCopyFlags)
    -> Result<SubmissionToken, Errno>;                       // OP 48
}
  • Equivalents: zero-copy variants of send/sendmsg.
  • ZeroCopyFlags: exposes REPORT_USAGE (IORING_SEND_ZC_REPORT_USAGE → the NOTIF completion indicates whether zero-copy actually took place or whether the kernel had to copy; readable via Completion::zero_copy_copied()). Bundle (RECVSEND_BUNDLE, FEAT_RECVSEND_BUNDLE) with provided buffers: Stage 3b.
  • When to use it: large payloads (≳ 10 KB, measure first); for small messages, ordinary send is simpler and often faster (zero-copy has a fixed notification-management overhead). Recommendation aligned with Principle 5 (measure).
  • Completion: see §4.1. Early failure ⇒ NOTIF without a preceding F_MORE: the façade then returns the buffer and propagates the error.

5. Teardown

5.1 submit_shutdown

#![allow(unused)]
fn main() {
pub fn submit_shutdown(&mut self, sock: BorrowedFd<'_>, how: ShutdownMode)
    -> Result<SubmissionToken, Errno>;                       // OP 34
}
  • Equivalent: shutdown. ShutdownMode: Read / Write / Both.
  • Completion: completed(). Errors: ENOTCONN, EINVAL.
  • Note: closing the FD itself is done via submit_close (Stage 2a) or the RAII drop of the OwnedFd.

6. Summary of the 12 operations

# opcodeOpcodeFaçadeCompletion
9SENDMSGsubmit_send_messageinto_result
10RECVMSGsubmit_receive_messageinto_receive_message_result
13ACCEPTsubmit_accept / _with_peeraccepted_fd / into_accept_result
16CONNECTsubmit_connectcompleted
26SENDsubmit_sendinto_buffer_result
27RECVsubmit_receiveinto_buffer_result
34SHUTDOWNsubmit_shutdowncompleted
45SOCKETsubmit_socketinto_socket_fd
47SEND_ZCsubmit_send_zero_copyinto_result + NOTIF into_zero_copy_buffer
48SENDMSG_ZCsubmit_send_message_zero_copyinto_result + NOTIF into_zero_copy_buffer
56BINDsubmit_bindcompleted
57LISTENsubmit_listencompleted

7. Added / shared types

Shared with family-net: SocketAddr, SocketDomain, SocketType, MessageFlags, ShutdownMode, AcceptFlags, SendMessageRequest, ReceiveMessageRequest, ControlMessages. New at Stage 2b: ZeroCopyFlags, ReceiveMessageMeta (backed by io_uring_recvmsg_out). New Completion methods: into_socket_fd, into_accept_result, into_receive_message_result, into_zero_copy_buffer, zero_copy_copied.


8. Test strategy

  • Integration (local sockets AF_UNIX + AF_INET loopback): accept/connect, send/recv round-trip, sendmsg/recvmsg with FD passing (send an FD, receive it, verify it points to the same object — core AirCom test), shutdown, socket+bind+listen chained (Stage 3c), accept of a direct fd.
  • Zero-copy: verify the sequence result (F_MORE) → NOTIF (F_NOTIF), that the buffer is not returned before NOTIF, that it is returned at NOTIF, the zero_copy_copied report, and the early-failure case (NOTIF only).
  • FD passing safety: MSG_CTRUNC (control buffer too small) ⇒ no FD leak; received FDs correctly wrapped as CLOEXEC OwnedFd.
  • Property-based: bytes sent/received ≤ buffer size; robustness of recvmsg_out parsing (kernel data = external input, Principle 3).
  • Errors via simulator: ECONNRESET, EPIPE, EAGAIN, EADDRINUSE.
  • Soundness: Miri on the zero-copy two-completion lifecycle (the buffer must neither be freed nor read between submission and NOTIF).
  • Coverage 100% lines + branches.

9. Key decisions that emerged at Stage 2b

  1. MSG_NOSIGNAL and SOCK_CLOEXEC by default — Air invariants, consistent across layer 0; explicit override possible.
  2. Explicit zero-copy two-completion lifecycle (extended S1) — a slot can outlive its first completion until CQE_F_NOTIF. The only such case in the module; documented and tested.
  3. First-class FD passing via sendmsg/recvmsg — this is the kernel materialization of AirCom capabilities (ADR-001). Received FDs are OwnedFd, never raw integers.
  4. bind/listen exposed even though “obvious” — indispensable for sockets as direct descriptors (without an ordinary FD).
  5. Direct/multishot/bundle variants deferred to Stages 3a/3d/3b — 2b remains focused on one-shot operations with clear ownership.

10. Next steps

Next spec: io-uring-2c-async-en.md (15 operations with no direct filesystem/network equivalent: nop, timeout, cancel, poll, futex, waitid, msg_ring, epoll_ctl, files_update, fixed_fd_install). Global English translation after validation of the French documents.


Document license: MPL 2.0 Status: Technical specification for Stage 2b (network) of the air-sys-syscall::io_uring module, target kernel 6.12 LTS.