Add vsock peer credentials on Posix.Context - #193
Conversation
Surfaces the peer's VSOCK context ID (CID) as `Posix.Context.vsockCredentials`. The CID is read through NIOPosix's `remoteVsockAddress` channel option rather than from `channel.remoteAddress`: `NIOCore.SocketAddress` has no vsock representation, so `remoteAddress` is always nil for vsock channels. The option reads the peer address from `getpeername` and validates the address family before returning it. `getPeerCID` returns nil for any channel that is not a connected vsock channel -- the option is rejected for other address families and is unsupported on non-POSIX channel types -- so `vsockCredentials` is nil for TCP and UDS connections. Requires the apple/swift-nio#3689 `remoteVsockAddress` channel option.
|
|
|
/easycla |
glbrntt
left a comment
There was a problem hiding this comment.
Thanks, we'll need a test for this as well.
- Inline `getPeerCID` at its single call site, removing the indirection. - Use `SocketAddress.VirtualSocket.ContextID` for the CID instead of a raw `UInt32`, reusing the existing gRPC type. - Make `VsockCredentials` `Hashable` with a `var` property. - Annotate the new API with `@available(gRPCSwiftNIOTransport 2.10, *)` and bump `nextMinorVersion` so the 2.10 availability macro is defined. - Drop the redundant second sentence of the `VsockCredentials` doc comment: the CID identifies whichever context is at the other end, not specifically a guest.
Covers both directions of the `vsockCredentials` contract with end-to-end RPCs, asserting on the server's transport-specific context from inside a server interceptor, which is the only place it's reachable. - Over vsock loopback, the credentials are populated and the CID is the local context. Gated on a new `vsockLoopbackAvailable()` helper: the existing `vsockAvailable()` only reports that the address family exists, which is enough to bind a listener, but connecting to `VMADDR_CID_LOCAL` also needs the `vsock_loopback` transport, so a connect test needs the stricter check. - Over a Unix domain socket and over IPv4, the credentials are nil. The CID is read with `getpeername`, which succeeds on any connected socket, so without an address-family check a UDS or TCP peer would yield a `sockaddr_un`/`sockaddr_in` reinterpreted as a `sockaddr_vm` and produce a meaningless CID. These two tests are what pin that down.
| /// The peer's VSOCK context ID for a virtual-socket connection, taken from the connection's | ||
| /// peer address. | ||
| @available(gRPCSwiftNIOTransport 2.10, *) | ||
| public struct VsockCredentials: Hashable, Sendable { |
There was a problem hiding this comment.
This can just be in the core module, I think. It's also not specific to the server transport so should be in its own file at the top level.
There was a problem hiding this comment.
Given we spell out the VirtualSocket for the address type we should do the same here: VirtualSocketCredentials
| public var cid: GRPCNIOTransportCore.SocketAddress.VirtualSocket.ContextID | ||
|
|
||
| public init(cid: GRPCNIOTransportCore.SocketAddress.VirtualSocket.ContextID) { |
There was a problem hiding this comment.
Let's spell this out as contextID like the VirtualSocket address does.
| // `channel.remoteAddress` can't report a vsock peer because NIO's `SocketAddress` has no | ||
| // vsock representation, so read the peer address from the channel option instead. It's | ||
| // rejected for other address families and unsupported on non-POSIX channels, both of which | ||
| // leave `vsockCredentials` nil. | ||
| if let vsockAddress = try? await channel.getOption(.remoteVsockAddress).get() { |
There was a problem hiding this comment.
Let's not do this unconditionally: we shouldn't pay the cost of attempting to look up a vsock address if we're not using vsock.
| return try await withThrowingTaskGroup(of: Void.self) { group in | ||
| let server = GRPCServer( | ||
| transport: HTTP2ServerTransport.Posix( | ||
| address: serverAddress, | ||
| transportSecurity: .plaintext | ||
| ), | ||
| services: [ControlService()], | ||
| interceptors: [RecordingInterceptor(recorder: recorder)] | ||
| ) | ||
|
|
||
| group.addTask { | ||
| try await server.serve() | ||
| } | ||
|
|
||
| let address = try await server.listeningAddress! | ||
|
|
||
| let client = GRPCClient( | ||
| transport: try HTTP2ClientTransport.Posix( | ||
| target: try makeTarget(address), | ||
| transportSecurity: .plaintext | ||
| ) | ||
| ) | ||
|
|
||
| group.addTask { | ||
| try await client.runConnections() | ||
| } | ||
|
|
||
| let control = ControlClient(wrapping: client) | ||
| let input = ControlInput.with { $0.numberOfMessages = 1 } | ||
| try await control.unary(request: ClientRequest(message: input)) { response in | ||
| _ = try response.message | ||
| } | ||
|
|
||
| server.beginGracefulShutdown() | ||
| client.beginGracefulShutdown() |
There was a problem hiding this comment.
Just use withGRPCServer and withGRPCClient here, no need to manually manage each set of resources.
There was a problem hiding this comment.
You can also use the HelloWorldService here whose implementation can be hooked on init, so the logic of each test can effectively live inside that and be driven by the client making a request. That removes the need for the recorder altogether.
Surfaces the peer's VSOCK context ID (CID) as
Posix.Context.vsockCredentials.The CID is read through NIOPosix's
remoteVsockAddresschannel option rather than fromchannel.remoteAddress:NIOCore.SocketAddresshas no vsock representation, soremoteAddressis always nil for vsock channels. The option reads the peer address fromgetpeernameand validates the address family before returning it.getPeerCIDreturns nil for any channel that is not a connected vsock channel -- the option is rejected for other address families and is unsupported on non-POSIX channel types -- sovsockCredentialsis nil for TCP and UDS connections.Requires the apple/swift-nio#3689
remoteVsockAddresschannel option.