📝 core: Document fd-passing wire contract and message association - #274
📝 core: Document fd-passing wire contract and message association#274cgwalters wants to merge 1 commit into
Conversation
The zlink fd-passing architecture is compatible with systemd's, but let's document this better. Assisted-by: OpenCode (Claude Opus 4.6) Signed-off-by: Colin Walters <walters@verbum.org>
zeenix
left a comment
There was a problem hiding this comment.
LGTM in general, thanks.
| //! Because the Varlink spec does not define FD passing, there is no formal standard for it. | ||
| //! systemd's `sd-varlink` has implemented it over `AF_UNIX` since 2023 and ships it across many | ||
| //! system services, making its design the de-facto convention for "FD passing over Varlink". | ||
| //! zlink deliberately follows that convention so the two interoperate (verified against a live | ||
| //! systemd service). Its rules: | ||
| //! |
There was a problem hiding this comment.
I think it'd be worthwhile to point out that this is based on (and identical to) how it's been done in D-Bus for more than a decades.
| //! * **`SO_PASSRIGHTS` opt-in (Linux >= 6.16).** Recent `sd-varlink` only *receives* FDs if it has | ||
| //! opted in; a peer that has not may have FDs it is sent silently dropped by the kernel. |
There was a problem hiding this comment.
Oh? That's a problem then because we don't enable it on sockets we create. With #273 merged recently, we now set SO_PASSCRED so we can easily set SO_PASSRIGHTS as well now.
There was a problem hiding this comment.
SO_PASSRIGHTS defaults to on for compatibility. I think this is more that it might make sense to have an API to disable it right?
There was a problem hiding this comment.
SO_PASSRIGHTSdefaults to on for compatibility.
Ah ok but then the wording needs to be better here as it currently sounds like we're saying it's opt-in but it's actually opt-out(?).
I think this is more that it might make sense to have an API to disable it right?
Nah, unless there is a reason to disable it? 🤔
| /// On completion, the number of bytes read and any file descriptors received are returned. | ||
| /// On completion, the number of bytes read and any file descriptors received are returned | ||
| /// (see [`ReadResult`]). |
There was a problem hiding this comment.
Oh, this should have been updated in 957c7c0 and we shouldn't describe the contents of ReadResult here IMO since that is a proper type now with it's own docs.
| /// | ||
| /// Any file descriptors returned here are taken to belong to the message whose first byte | ||
| /// arrives in this read. The [`connection`](crate::connection#file-descriptor-passing) module | ||
| /// docs describe the full FD-to-message association contract; in short, implementers must | ||
| /// return the FDs received by a given `recvmsg` together with the bytes received by that same | ||
| /// `recvmsg`, so the positional association upheld by [`ReadConnection`](super::ReadConnection) | ||
| /// is correct. |
There was a problem hiding this comment.
- IMO this should go into
ReadResultorReadResult::fdsdocs. - While we're talking to implementers here, we should point out the
doc(hidden)utility API they can (and perhaps should) use here.
| /// `buf` (e.g. via `SCM_RIGHTS`), so that the receiver can associate them with the message | ||
| /// starting at that byte. [`WriteConnection`](super::WriteConnection) only ever calls this with | ||
| /// `fds` for a `buf` that begins at a message boundary; see the | ||
| /// [`connection`](crate::connection#file-descriptor-passing) module docs for the full contract. |
There was a problem hiding this comment.
Similar/same comments here as for read above.
Closes: varlink#43 The systemd varlink implementation is the de-facto standard for fd passing over varlink. See z-galaxy/zlink#274 for example. Let's add an implementation of this. The receive path is the tricky part. Ancillary data is bound to the exact recvmsg(2) that delivers the accompanying bytes, so the fd-aware reader shares the connection's unified read buffer (introduced in the prep commit) rather than maintaining a parallel one. Both ReadBytes and ReadBytesWithFDs draw from the same Conn.readBuf, so the two read paths no longer strand each other's buffered bytes. Received fds are keyed to the message containing the first byte of their recvmsg, matching how the sender attaches them. The API is additive: existing Send/Call/Reply keep working unchanged. New SendWithFDs/CallWithFDs/ReplyWithFDs/RequestFDs surface fds, and an optional FDReadWriter interface lets non-unix transports (tcp, the bridge pipe) degrade gracefully by returning ErrFDsUnsupported. The SCM_RIGHTS syscalls live in unix-tagged files with a !unix stub so the Windows build keeps compiling. No new module dependency is added; only the stdlib syscall/net/os packages are used. Assisted-by: OpenCode (Claude Opus 4)
Closes: varlink#43 The systemd varlink implementation is the de-facto standard for fd passing over varlink. See z-galaxy/zlink#274 for example. Let's add an implementation of this. The receive path is the tricky part. Ancillary data is bound to the exact recvmsg(2) that delivers the accompanying bytes, so the fd-aware reader shares the connection's unified read buffer (introduced in the prep commit) rather than maintaining a parallel one. Both ReadBytes and ReadBytesWithFDs draw from the same Conn.readBuf, so the two read paths no longer strand each other's buffered bytes. Received fds are keyed to the message containing the LAST byte of their recvmsg. The kernel ends its stream-receive loop as soon as it attaches an skb's fds, so the fd-bearing bytes always end at the recvmsg's last byte, which is the only anchor that reliably lands in the owning message. Keying on the first byte is unsound: the kernel can coalesce a preceding fd-less message in front of the fd-bearing one, putting the first byte in the wrong message and stealing its fds. The API is additive: existing Send/Call/Reply keep working unchanged. New SendWithFDs/CallWithFDs/ReplyWithFDs/RequestFDs surface fds, and an optional FDReadWriter interface lets non-unix transports (tcp, the bridge pipe) degrade gracefully by returning ErrFDsUnsupported. The SCM_RIGHTS syscalls live in unix-tagged files with a !unix stub so the Windows build keeps compiling. No new module dependency is added; only the stdlib syscall/net/os packages are used. Assisted-by: OpenCode (Claude Opus 4)
Closes: varlink#43 The systemd varlink implementation is the de-facto standard for fd passing over varlink. See z-galaxy/zlink#274 for example. Let's add an implementation of this. The receive path is the tricky part. Ancillary data is bound to the exact recvmsg(2) that delivers the accompanying bytes, so the fd-aware reader shares the connection's unified read buffer (introduced in the prep commit) rather than maintaining a parallel one. Both ReadBytes and ReadBytesWithFDs draw from the same Conn.readBuf, so the two read paths no longer strand each other's buffered bytes. Received fds are keyed to the message containing the LAST byte of their recvmsg. The kernel ends its stream-receive loop as soon as it attaches an skb's fds, so the fd-bearing bytes always end at the recvmsg's last byte, which is the only anchor that reliably lands in the owning message. Keying on the first byte is unsound: the kernel can coalesce a preceding fd-less message in front of the fd-bearing one, putting the first byte in the wrong message and stealing its fds. The API is additive: existing Send/Call/Reply keep working unchanged. New SendWithFDs/CallWithFDs/ReplyWithFDs/RequestFDs surface fds, and an optional FDReadWriter interface lets non-unix transports (tcp, the bridge pipe) degrade gracefully by returning ErrFDsUnsupported. The SCM_RIGHTS syscalls live in unix-tagged files with a !unix stub so the Windows build keeps compiling. No new module dependency is added; only the stdlib syscall/net/os packages are used. Assisted-by: OpenCode (Claude Opus 4)
Closes: #43 The systemd varlink implementation is the de-facto standard for fd passing over varlink. See z-galaxy/zlink#274 for example. Let's add an implementation of this. The receive path is the tricky part. Ancillary data is bound to the exact recvmsg(2) that delivers the accompanying bytes, so the fd-aware reader shares the connection's unified read buffer (introduced in the prep commit) rather than maintaining a parallel one. Both ReadBytes and ReadBytesWithFDs draw from the same Conn.readBuf, so the two read paths no longer strand each other's buffered bytes. Received fds are keyed to the message containing the LAST byte of their recvmsg. The kernel ends its stream-receive loop as soon as it attaches an skb's fds, so the fd-bearing bytes always end at the recvmsg's last byte, which is the only anchor that reliably lands in the owning message. Keying on the first byte is unsound: the kernel can coalesce a preceding fd-less message in front of the fd-bearing one, putting the first byte in the wrong message and stealing its fds. The API is additive: existing Send/Call/Reply keep working unchanged. New SendWithFDs/CallWithFDs/ReplyWithFDs/RequestFDs surface fds, and an optional FDReadWriter interface lets non-unix transports (tcp, the bridge pipe) degrade gracefully by returning ErrFDsUnsupported. The SCM_RIGHTS syscalls live in unix-tagged files with a !unix stub so the Windows build keeps compiling. No new module dependency is added; only the stdlib syscall/net/os packages are used. Assisted-by: OpenCode (Claude Opus 4)
|
@cgwalters Hi. Do you think you can finish this (other your other PRs) soon? |
The zlink fd-passing architecture is compatible with systemd's, but let's document this better.
Assisted-by: OpenCode (Claude Opus 4.8)