Add channel options for a vsock socket's local and remote addresses - #3689
Add channel options for a vsock socket's local and remote addresses#3689VictorDebray wants to merge 5 commits into
Conversation
|
|
||
| extension ChannelOptions { | ||
| /// - seealso: `LocalVsockAddress` | ||
| public static let localVsockAddress = Types.LocalVsockAddress() |
There was a problem hiding this comment.
this can also be a static computed var
There was a problem hiding this comment.
| /// | ||
| /// `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. |
There was a problem hiding this comment.
This is unnecessary context
There was a problem hiding this comment.
|
|
||
| extension ChannelOptions { | ||
| /// - seealso: `RemoteVsockAddress` | ||
| public static let remoteVsockAddress = Types.RemoteVsockAddress() |
There was a problem hiding this comment.
This can be a static computed var
There was a problem hiding this comment.
| /// | ||
| /// `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. |
There was a problem hiding this comment.
This is unnecessary context
There was a problem hiding this comment.
| /// | ||
| /// ``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. |
There was a problem hiding this comment.
This is unnecessary context
There was a problem hiding this comment.
|
|
||
| let address = try socket.getLocalVsockAddress() | ||
| XCTAssertNotEqual(address.port, .any, "The kernel should have assigned a concrete port") | ||
| XCTAssertEqual(address.cid, try socket.getLocalVsockContextID()) |
There was a problem hiding this comment.
Can you remove this? We're not testing getLocalVsockContextID here.
There was a problem hiding this comment.
| /// A non-vsock socket must not report a vsock local address, for the same reason | ||
| /// ``testGetRemoteVsockAddressRejectsNonVsockSocket()`` covers on the peer side. |
There was a problem hiding this comment.
| /// 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. |
There was a problem hiding this comment.
| /// 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`. |
There was a problem hiding this comment.
| // Read the peer address on the accepted channel. Sending the address rather than the | ||
| // channel through the promise keeps this Sendable-clean. |
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.
There was a problem hiding this comment.
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.
cd798aa to
770febe
Compare
|
Addressed all reviews! |
|
Address all comment reviews and make remote and localvsock a var. |
| var addr = sockaddr_vm() | ||
| var len = socklen_t(MemoryLayout<sockaddr_vm>.size) | ||
| try withUnsafeMutablePointer(to: &addr) { addrPtr in | ||
| try addrPtr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in | ||
| try getName(fd, sockaddrPtr, &len) | ||
| } | ||
| } |
There was a problem hiding this comment.
sockaddr_vm and sockaddr are different sizes on Darwin (12 vs. 16; they're both 16 on Linux), so this rebinds out of bounds. This should use sockaddr_storage:
var addr = sockaddr_storage()
try addr.withMutableSockAddr { addrPtr, size in
var size = socklen_t(size)
try self.withUnsafeHandle {
try getName($0, addrPtr, &size)
}
}
You can then call convert() to get the sockaddr_vm.
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