Skip to content

Commit

Permalink
Add bind and connect APIs for VSOCK sockets (#1636)
Browse files Browse the repository at this point in the history
Motivation:

The VSOCK address family facilitates communication between virtual machines and the host they are running that need a communications channel that is independent of virtual machine network configuration.

While both GRPC has support for building channels using sockets that were created out of band (`withConnectedSocket(_:)` and `withBoundSocket(_:)`, there are no APIs that facilitate the creation of VSOCK-based channels.

apple/swift-nio#2479 adds a `VsockAddress` type and associated bootstrap APIs, but these need corresponding APIs here to be used in GRPC clients and servers.

Modifications:

- Add `ConnectionTarget.vsockAddress` and `BindTarget.vsockAddress `.
- Add `ClientBootstrapProtocol connect(to vsockAddress:)` and `ServerBootstrapProtocol.bind(to vsockAddress:)`.
- Add `Server.bind(vsockAddress: VsockAddress)`.

Signed-off-by: Si Beaumont <[email protected]>
  • Loading branch information
simonjbeaumont authored Aug 8, 2023
1 parent bf6065f commit 5e3e3f5
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let includeNIOSSL = ProcessInfo.processInfo.environment["GRPC_NO_NIO_SSL"] == ni
let packageDependencies: [Package.Dependency] = [
.package(
url: "https://github.com/apple/swift-nio.git",
from: "2.42.0"
from: "2.58.0"
),
.package(
url: "https://github.com/apple/swift-nio-http2.git",
Expand Down
11 changes: 11 additions & 0 deletions Sources/GRPC/ClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Logging
import NIOCore
import NIOHPACK
import NIOHTTP2
import NIOPosix
#if canImport(NIOSSL)
import NIOSSL
#endif
Expand Down Expand Up @@ -269,6 +270,7 @@ public struct ConnectionTarget: Sendable {
case unixDomainSocket(String)
case socketAddress(SocketAddress)
case connectedSocket(NIOBSDSocket.Handle)
case vsockAddress(VsockAddress)
}

internal var wrapped: Wrapped
Expand Down Expand Up @@ -301,6 +303,11 @@ public struct ConnectionTarget: Sendable {
return ConnectionTarget(.connectedSocket(socket))
}

/// A vsock socket.
public static func vsockAddress(_ vsockAddress: VsockAddress) -> ConnectionTarget {
return ConnectionTarget(.vsockAddress(vsockAddress))
}

@usableFromInline
var host: String {
switch self.wrapped {
Expand All @@ -312,6 +319,8 @@ public struct ConnectionTarget: Sendable {
return address.host
case .unixDomainSocket, .socketAddress(.unixDomainSocket), .connectedSocket:
return "localhost"
case let .vsockAddress(address):
return "vsock://\(address.cid)"
}
}
}
Expand Down Expand Up @@ -552,6 +561,8 @@ extension ClientBootstrapProtocol {
return self.connect(to: address)
case let .connectedSocket(socket):
return self.withConnectedSocket(socket)
case let .vsockAddress(address):
return self.connect(to: address)
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion Sources/GRPC/PlatformSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public protocol ClientBootstrapProtocol {
func connect(host: String, port: Int) -> EventLoopFuture<Channel>
func connect(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel>
func connect(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel>

func connectTimeout(_ timeout: TimeAmount) -> Self
func channelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
Expand All @@ -144,6 +145,10 @@ extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {
public func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel> {
preconditionFailure("NIOTSConnectionBootstrap does not support withConnectedSocket(_:)")
}

public func connect(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel> {
preconditionFailure("NIOTSConnectionBootstrap does not support connect(to vsockAddress:)")
}
}
#endif

Expand All @@ -154,6 +159,7 @@ public protocol ServerBootstrapProtocol {
func bind(host: String, port: Int) -> EventLoopFuture<Channel>
func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
func withBoundSocket(_ connectedSocket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel>
func bind(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel>

#if swift(>=5.7)
@preconcurrency
Expand Down Expand Up @@ -189,7 +195,11 @@ extension ServerBootstrap: ServerBootstrapProtocol {}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSListenerBootstrap: ServerBootstrapProtocol {
public func withBoundSocket(_ connectedSocket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel> {
preconditionFailure("NIOTSListenerBootstrap does not support withConnectedSocket(_:)")
preconditionFailure("NIOTSListenerBootstrap does not support withBoundSocket(_:)")
}

public func bind(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel> {
preconditionFailure("NIOTSListenerBootstrap does not support bind(to vsockAddress:)")
}
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions Sources/GRPC/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,9 @@ extension ServerBootstrapProtocol {

case let .connectedSocket(socket):
return self.withBoundSocket(socket)

case let .vsockAddress(address):
return self.bind(to: address)
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/GRPC/ServerBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
import Logging
import NIOCore
import NIOPosix

#if canImport(Network)
import Security
Expand Down Expand Up @@ -60,6 +61,12 @@ extension Server {
self.configuration.tlsConfiguration = self.maybeTLS
return Server.start(configuration: self.configuration)
}

public func bind(vsockAddress: VsockAddress) -> EventLoopFuture<Server> {
self.configuration.target = .vsockAddress(vsockAddress)
self.configuration.tlsConfiguration = self.maybeTLS
return Server.start(configuration: self.configuration)
}
}
}

Expand Down
67 changes: 67 additions & 0 deletions Tests/GRPCTests/VsockSocketTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import EchoImplementation
import EchoModel
import GRPC
import NIOPosix
import XCTest

class VsockSocketTests: GRPCTestCase {
func testVsockSocket() throws {
try XCTSkipUnless(self.vsockAvailable(), "Vsock unavailable")
let group = NIOPosix.MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try group.syncShutdownGracefully())
}

// Setup a server.
let server = try Server.insecure(group: group)
.withServiceProviders([EchoProvider()])
.withLogger(self.serverLogger)
.bind(vsockAddress: .init(cid: .any, port: 31234))
.wait()
defer {
XCTAssertNoThrow(try server.close().wait())
}

let channel = try GRPCChannelPool.with(
target: .vsockAddress(.init(cid: .local, port: 31234)),
transportSecurity: .plaintext,
eventLoopGroup: group
)
defer {
XCTAssertNoThrow(try channel.close().wait())
}

let client = Echo_EchoNIOClient(channel: channel)
let resp = try client.get(Echo_EchoRequest(text: "Hello")).response.wait()
XCTAssertEqual(resp.text, "Swift echo get: Hello")
}

private func vsockAvailable() -> Bool {
let fd: CInt
#if os(Linux)
fd = socket(AF_VSOCK, CInt(SOCK_STREAM.rawValue), 0)
#elseif canImport(Darwin)
fd = socket(AF_VSOCK, SOCK_STREAM, 0)
#else
fd = -1
#endif
if fd == -1 { return false }
precondition(close(fd) == 0)
return true
}
}

0 comments on commit 5e3e3f5

Please sign in to comment.