diff --git a/Package.swift b/Package.swift index 473622c25..6e3f2f007 100644 --- a/Package.swift +++ b/Package.swift @@ -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", diff --git a/Sources/GRPC/ClientConnection.swift b/Sources/GRPC/ClientConnection.swift index e3ac756e0..ef997ad30 100644 --- a/Sources/GRPC/ClientConnection.swift +++ b/Sources/GRPC/ClientConnection.swift @@ -23,6 +23,7 @@ import Logging import NIOCore import NIOHPACK import NIOHTTP2 +import NIOPosix #if canImport(NIOSSL) import NIOSSL #endif @@ -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 @@ -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 { @@ -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)" } } } @@ -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) } } } diff --git a/Sources/GRPC/PlatformSupport.swift b/Sources/GRPC/PlatformSupport.swift index 6f9b1a163..0ecfcd97d 100644 --- a/Sources/GRPC/PlatformSupport.swift +++ b/Sources/GRPC/PlatformSupport.swift @@ -118,6 +118,7 @@ public protocol ClientBootstrapProtocol { func connect(host: String, port: Int) -> EventLoopFuture func connect(unixDomainSocketPath: String) -> EventLoopFuture func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture + func connect(to vsockAddress: VsockAddress) -> EventLoopFuture func connectTimeout(_ timeout: TimeAmount) -> Self func channelOption(_ option: T, value: T.Value) -> Self where T: ChannelOption @@ -144,6 +145,10 @@ extension NIOTSConnectionBootstrap: ClientBootstrapProtocol { public func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> EventLoopFuture { preconditionFailure("NIOTSConnectionBootstrap does not support withConnectedSocket(_:)") } + + public func connect(to vsockAddress: VsockAddress) -> EventLoopFuture { + preconditionFailure("NIOTSConnectionBootstrap does not support connect(to vsockAddress:)") + } } #endif @@ -154,6 +159,7 @@ public protocol ServerBootstrapProtocol { func bind(host: String, port: Int) -> EventLoopFuture func bind(unixDomainSocketPath: String) -> EventLoopFuture func withBoundSocket(_ connectedSocket: NIOBSDSocket.Handle) -> EventLoopFuture + func bind(to vsockAddress: VsockAddress) -> EventLoopFuture #if swift(>=5.7) @preconcurrency @@ -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 { - preconditionFailure("NIOTSListenerBootstrap does not support withConnectedSocket(_:)") + preconditionFailure("NIOTSListenerBootstrap does not support withBoundSocket(_:)") + } + + public func bind(to vsockAddress: VsockAddress) -> EventLoopFuture { + preconditionFailure("NIOTSListenerBootstrap does not support bind(to vsockAddress:)") } } #endif diff --git a/Sources/GRPC/Server.swift b/Sources/GRPC/Server.swift index 7b16c9232..5ba505954 100644 --- a/Sources/GRPC/Server.swift +++ b/Sources/GRPC/Server.swift @@ -538,6 +538,9 @@ extension ServerBootstrapProtocol { case let .connectedSocket(socket): return self.withBoundSocket(socket) + + case let .vsockAddress(address): + return self.bind(to: address) } } } diff --git a/Sources/GRPC/ServerBuilder.swift b/Sources/GRPC/ServerBuilder.swift index 1600335c8..8f98520ee 100644 --- a/Sources/GRPC/ServerBuilder.swift +++ b/Sources/GRPC/ServerBuilder.swift @@ -15,6 +15,7 @@ */ import Logging import NIOCore +import NIOPosix #if canImport(Network) import Security @@ -60,6 +61,12 @@ extension Server { self.configuration.tlsConfiguration = self.maybeTLS return Server.start(configuration: self.configuration) } + + public func bind(vsockAddress: VsockAddress) -> EventLoopFuture { + self.configuration.target = .vsockAddress(vsockAddress) + self.configuration.tlsConfiguration = self.maybeTLS + return Server.start(configuration: self.configuration) + } } } diff --git a/Tests/GRPCTests/VsockSocketTests.swift b/Tests/GRPCTests/VsockSocketTests.swift new file mode 100644 index 000000000..f9bc09c30 --- /dev/null +++ b/Tests/GRPCTests/VsockSocketTests.swift @@ -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 + } +}