Skip to content

Commit 5e3e3f5

Browse files
Add bind and connect APIs for VSOCK sockets (#1636)
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]>
1 parent bf6065f commit 5e3e3f5

File tree

6 files changed

+100
-2
lines changed

6 files changed

+100
-2
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ let includeNIOSSL = ProcessInfo.processInfo.environment["GRPC_NO_NIO_SSL"] == ni
3232
let packageDependencies: [Package.Dependency] = [
3333
.package(
3434
url: "https://github.com/apple/swift-nio.git",
35-
from: "2.42.0"
35+
from: "2.58.0"
3636
),
3737
.package(
3838
url: "https://github.com/apple/swift-nio-http2.git",

Sources/GRPC/ClientConnection.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Logging
2323
import NIOCore
2424
import NIOHPACK
2525
import NIOHTTP2
26+
import NIOPosix
2627
#if canImport(NIOSSL)
2728
import NIOSSL
2829
#endif
@@ -269,6 +270,7 @@ public struct ConnectionTarget: Sendable {
269270
case unixDomainSocket(String)
270271
case socketAddress(SocketAddress)
271272
case connectedSocket(NIOBSDSocket.Handle)
273+
case vsockAddress(VsockAddress)
272274
}
273275

274276
internal var wrapped: Wrapped
@@ -301,6 +303,11 @@ public struct ConnectionTarget: Sendable {
301303
return ConnectionTarget(.connectedSocket(socket))
302304
}
303305

306+
/// A vsock socket.
307+
public static func vsockAddress(_ vsockAddress: VsockAddress) -> ConnectionTarget {
308+
return ConnectionTarget(.vsockAddress(vsockAddress))
309+
}
310+
304311
@usableFromInline
305312
var host: String {
306313
switch self.wrapped {
@@ -312,6 +319,8 @@ public struct ConnectionTarget: Sendable {
312319
return address.host
313320
case .unixDomainSocket, .socketAddress(.unixDomainSocket), .connectedSocket:
314321
return "localhost"
322+
case let .vsockAddress(address):
323+
return "vsock://\(address.cid)"
315324
}
316325
}
317326
}
@@ -552,6 +561,8 @@ extension ClientBootstrapProtocol {
552561
return self.connect(to: address)
553562
case let .connectedSocket(socket):
554563
return self.withConnectedSocket(socket)
564+
case let .vsockAddress(address):
565+
return self.connect(to: address)
555566
}
556567
}
557568
}

Sources/GRPC/PlatformSupport.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public protocol ClientBootstrapProtocol {
118118
func connect(host: String, port: Int) -> EventLoopFuture<Channel>
119119
func connect(unixDomainSocketPath: String) -> EventLoopFuture<Channel>
120120
func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel>
121+
func connect(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel>
121122

122123
func connectTimeout(_ timeout: TimeAmount) -> Self
123124
func channelOption<T>(_ option: T, value: T.Value) -> Self where T: ChannelOption
@@ -144,6 +145,10 @@ extension NIOTSConnectionBootstrap: ClientBootstrapProtocol {
144145
public func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel> {
145146
preconditionFailure("NIOTSConnectionBootstrap does not support withConnectedSocket(_:)")
146147
}
148+
149+
public func connect(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel> {
150+
preconditionFailure("NIOTSConnectionBootstrap does not support connect(to vsockAddress:)")
151+
}
147152
}
148153
#endif
149154

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

158164
#if swift(>=5.7)
159165
@preconcurrency
@@ -189,7 +195,11 @@ extension ServerBootstrap: ServerBootstrapProtocol {}
189195
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
190196
extension NIOTSListenerBootstrap: ServerBootstrapProtocol {
191197
public func withBoundSocket(_ connectedSocket: NIOBSDSocket.Handle) -> EventLoopFuture<Channel> {
192-
preconditionFailure("NIOTSListenerBootstrap does not support withConnectedSocket(_:)")
198+
preconditionFailure("NIOTSListenerBootstrap does not support withBoundSocket(_:)")
199+
}
200+
201+
public func bind(to vsockAddress: VsockAddress) -> EventLoopFuture<Channel> {
202+
preconditionFailure("NIOTSListenerBootstrap does not support bind(to vsockAddress:)")
193203
}
194204
}
195205
#endif

Sources/GRPC/Server.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,9 @@ extension ServerBootstrapProtocol {
538538

539539
case let .connectedSocket(socket):
540540
return self.withBoundSocket(socket)
541+
542+
case let .vsockAddress(address):
543+
return self.bind(to: address)
541544
}
542545
}
543546
}

Sources/GRPC/ServerBuilder.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
import Logging
1717
import NIOCore
18+
import NIOPosix
1819

1920
#if canImport(Network)
2021
import Security
@@ -60,6 +61,12 @@ extension Server {
6061
self.configuration.tlsConfiguration = self.maybeTLS
6162
return Server.start(configuration: self.configuration)
6263
}
64+
65+
public func bind(vsockAddress: VsockAddress) -> EventLoopFuture<Server> {
66+
self.configuration.target = .vsockAddress(vsockAddress)
67+
self.configuration.tlsConfiguration = self.maybeTLS
68+
return Server.start(configuration: self.configuration)
69+
}
6370
}
6471
}
6572

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2022, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import EchoImplementation
17+
import EchoModel
18+
import GRPC
19+
import NIOPosix
20+
import XCTest
21+
22+
class VsockSocketTests: GRPCTestCase {
23+
func testVsockSocket() throws {
24+
try XCTSkipUnless(self.vsockAvailable(), "Vsock unavailable")
25+
let group = NIOPosix.MultiThreadedEventLoopGroup(numberOfThreads: 1)
26+
defer {
27+
XCTAssertNoThrow(try group.syncShutdownGracefully())
28+
}
29+
30+
// Setup a server.
31+
let server = try Server.insecure(group: group)
32+
.withServiceProviders([EchoProvider()])
33+
.withLogger(self.serverLogger)
34+
.bind(vsockAddress: .init(cid: .any, port: 31234))
35+
.wait()
36+
defer {
37+
XCTAssertNoThrow(try server.close().wait())
38+
}
39+
40+
let channel = try GRPCChannelPool.with(
41+
target: .vsockAddress(.init(cid: .local, port: 31234)),
42+
transportSecurity: .plaintext,
43+
eventLoopGroup: group
44+
)
45+
defer {
46+
XCTAssertNoThrow(try channel.close().wait())
47+
}
48+
49+
let client = Echo_EchoNIOClient(channel: channel)
50+
let resp = try client.get(Echo_EchoRequest(text: "Hello")).response.wait()
51+
XCTAssertEqual(resp.text, "Swift echo get: Hello")
52+
}
53+
54+
private func vsockAvailable() -> Bool {
55+
let fd: CInt
56+
#if os(Linux)
57+
fd = socket(AF_VSOCK, CInt(SOCK_STREAM.rawValue), 0)
58+
#elseif canImport(Darwin)
59+
fd = socket(AF_VSOCK, SOCK_STREAM, 0)
60+
#else
61+
fd = -1
62+
#endif
63+
if fd == -1 { return false }
64+
precondition(close(fd) == 0)
65+
return true
66+
}
67+
}

0 commit comments

Comments
 (0)