Add channel options for a vsock socket's local and remote addresses - #3689
Open
VictorDebray wants to merge 4 commits into
Open
Add channel options for a vsock socket's local and remote addresses#3689VictorDebray wants to merge 4 commits into
VictorDebray wants to merge 4 commits into
Conversation
Exposes an accepted vsock connection's peer address -- and therefore the peer context ID (CID) -- without changing `NIOCore.SocketAddress`. `Channel/remoteAddress` returns `SocketAddress?`, which has no vsock representation, so it is always nil for vsock channels. Rather than add a `.vsock` case to that public enum (a source-breaking change for every exhaustive switch over it, in NIO and downstream), this mirrors the pattern NIO already uses for the same problem on the local side: `localVsockContextID` is a get-only `ChannelOption` precisely because `localAddress` cannot report a vsock address either. The helper validates the address family before trusting the result. On a non-vsock socket `getpeername` fills a different `sockaddr`, and reinterpreting those bytes would yield a context ID derived from unrelated data such as a peer's IP address and port. Callers use the CID for trust decisions, so a mismatch throws `SocketAddressError.unsupported` instead. A socketpair-based test covers this and fails if the guard is removed. All additive: a new option type, a new static accessor, and a new arm in an internal switch. No existing declaration changes, so no exhaustive switch anywhere needs updating.
Covers the path the option exists for: a live vsock connection over loopback, where `getpeername` really does fill a `sockaddr_vm`. The client asserts its peer's port is the one the listener bound, which is what would catch a misread `sockaddr_vm. Wrong field offsets could not happen to reproduce the chosen port. The accepted channel then asserts reading its peer succeeds and reports the same CID the client saw, since both ends of a loopback connection live in the same context. Gated on `System.supportsVsockLoopback` in the same way as `testGetLocalCID` and `testEchoVsock`, so it skips on hosts without the transport and runs in the `vsock-tests` CI job, which loads `vsock_loopback` and fails on skipped tests.
Mirrors remoteVsockAddress: exposes a vsock socket's local address (including bound port) since Channel/localAddress can't represent vsock addresses and LocalVsockContextID alone can't report the port chosen for Port/any binds.
glbrntt
requested changes
Aug 24, 2026
|
|
||
| extension ChannelOptions { | ||
| /// - seealso: `LocalVsockAddress` | ||
| public static let localVsockAddress = Types.LocalVsockAddress() |
Contributor
There was a problem hiding this comment.
this can also be a static computed var
Comment on lines
+194
to
+200
| /// | ||
| /// `Channel/localAddress` cannot report a vsock address, because `SocketAddress` has no vsock | ||
| /// representation. This option exposes the address that `getsockname` reports for the socket | ||
| /// instead. | ||
| /// | ||
| /// ``LocalVsockContextID`` reports only the context ID, so it can't tell you which port a | ||
| /// listener bound to when it was bound to `VsockAddress/Port/any`. This option reports both. |
Contributor
There was a problem hiding this comment.
This is unnecessary context
|
|
||
| extension ChannelOptions { | ||
| /// - seealso: `RemoteVsockAddress` | ||
| public static let remoteVsockAddress = Types.RemoteVsockAddress() |
Contributor
There was a problem hiding this comment.
This can be a static computed var
Comment on lines
+218
to
+223
| /// | ||
| /// `Channel/remoteAddress` cannot report a vsock peer, because `SocketAddress` has no vsock | ||
| /// representation. This option exposes the peer address that `getpeername` reports for the | ||
| /// connection instead, which is where the peer's context ID (CID) comes from. | ||
| /// | ||
| /// Only meaningful on a connected vsock channel; getting it on a listening channel fails. |
Contributor
There was a problem hiding this comment.
This is unnecessary context
Comment on lines
+343
to
+345
| /// | ||
| /// ``getLocalVsockContextID()`` reports only the context ID, so it can't say which port a | ||
| /// listener bound to when it was bound to `VsockAddress/Port/any`. This reports both. |
Contributor
There was a problem hiding this comment.
This is unnecessary context
|
|
||
| let address = try socket.getLocalVsockAddress() | ||
| XCTAssertNotEqual(address.port, .any, "The kernel should have assigned a concrete port") | ||
| XCTAssertEqual(address.cid, try socket.getLocalVsockContextID()) |
Contributor
There was a problem hiding this comment.
Can you remove this? We're not testing getLocalVsockContextID here.
Comment on lines
+117
to
+118
| /// A non-vsock socket must not report a vsock local address, for the same reason | ||
| /// ``testGetRemoteVsockAddressRejectsNonVsockSocket()`` covers on the peer side. |
Comment on lines
+131
to
+135
| /// A non-vsock socket must not report a vsock peer address. | ||
| /// | ||
| /// `getpeername` on a UDS socket fills a `sockaddr_un`; if those bytes were reinterpreted as a | ||
| /// `sockaddr_vm` the option would return a context ID derived from unrelated memory. Callers use | ||
| /// the CID to make trust decisions, so this must fail instead. |
Comment on lines
+158
to
+162
| /// Both ends of a real vsock connection report the peer's address. | ||
| /// | ||
| /// This is the path the option exists for, so it needs a live connection: it exercises | ||
| /// `getpeername` on an `AF_VSOCK` socket and the reinterpretation of the resulting | ||
| /// `sockaddr_vm`. |
Comment on lines
+171
to
+172
| // Read the peer address on the accepted channel. Sending the address rather than the | ||
| // channel through the promise keeps this Sendable-clean. |
Contributor
There was a problem hiding this comment.
I think this comment is wrong. Channel is Sendable so there's nothing about sending a channel through a promise that would make this sendable-unclean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add channel options to read a vsock socket's local and remote addresses
Motivation
Channel.remoteAddressandChannel.localAddressare alwaysnilon vsock channels.NIOCore.SocketAddresshas no vsock representation. NIOPosix otherwise supports vsock throughVsockAddress,ServerBootstrap.bind(to:)andClientBootstrap.connect(to:).So a server accepting a vsock connection can't find out which context (VM) connected. A listener bound to
VsockAddress/Port/anycan't find out which port the kernel gave it.The peer's context ID is the only identity a vsock connection carries. The kernel supplies it, not the peer. That makes it the natural thing to key authorization on.
ChannelOptions.localVsockContextIDalready exists for the same reason on the local side. It reports the context ID without the port. A wildcard bind therefore still leaves servers and tests hard-coding a port and racing for it.Modifications
Add
ChannelOptions.Types.RemoteVsockAddressandLocalVsockAddress. Both are get-only options withValue == VsockAddress.Add
BaseSocket.getRemoteVsockAddress()andgetLocalVsockAddress(). On a non-vsock socket these calls fill a differentsockaddr. Reinterpreting those bytes would yield a context ID derived from unrelated data, such as an IP address and port. Callers key authorization on the CID, so failing beats returning something meaningless.Service both in
SocketChannel.getOption0.LocalVsockAddressis serviced onServerSocketChanneltoo, which is the case which needs it.RemoteVsockAddressisn't: a listener has no peer.Tests: both options are rejected on a connected non-vsock socket. Over vsock loopback, a client's peer port is the port the listener bound, and the accepted channel reports the same CID the client saw. A listener bound to
Port/anyreports a concrete port and a context ID matchinggetLocalVsockContextID(), and the channel option agrees with the socket. The loopback tests are gated onSystem.supportsVsockLoopback, matchingtestGetLocalCIDandtestEchoVsock.Result