Skip to content

📝 core: Document fd-passing wire contract and message association - #274

Open
cgwalters wants to merge 1 commit into
z-galaxy:mainfrom
cgwalters:docs-fd-passing-semantics
Open

📝 core: Document fd-passing wire contract and message association#274
cgwalters wants to merge 1 commit into
z-galaxy:mainfrom
cgwalters:docs-fd-passing-semantics

Conversation

@cgwalters

Copy link
Copy Markdown
Contributor

The zlink fd-passing architecture is compatible with systemd's, but let's document this better.

Assisted-by: OpenCode (Claude Opus 4.8)

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>
@codspeed-hq

codspeed-hq Bot commented Jun 4, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 4 untouched benchmarks


Comparing cgwalters:docs-fd-passing-semantics (250839f) with main (f8d70b4)

Open in CodSpeed

@zeenix zeenix left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in general, thanks.

Comment on lines +49 to +54
//! 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:
//!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +60 to +61
//! * **`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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SO_PASSRIGHTS defaults 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? 🤔

Comment on lines -30 to +31
/// 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`]).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +32 to +38
///
/// 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • IMO this should go into ReadResult or ReadResult::fds docs.
  • 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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar/same comments here as for read above.

cgwalters added a commit to cgwalters/varlink-go that referenced this pull request Jun 11, 2026
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)
cgwalters added a commit to cgwalters/varlink-go that referenced this pull request Jun 11, 2026
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)
cgwalters added a commit to cgwalters/varlink-go that referenced this pull request Jul 9, 2026
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)
noxiouz pushed a commit to varlink/go that referenced this pull request Jul 9, 2026
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)
@zeenix

zeenix commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

@cgwalters Hi. Do you think you can finish this (other your other PRs) soon?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants