GRPCNetworkFrameworkTests.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright 2021, 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. #if canImport(NIOSSL)
  17. #if canImport(Network)
  18. import Dispatch
  19. import EchoImplementation
  20. import EchoModel
  21. import GRPC
  22. import Network
  23. import NIOCore
  24. import NIOPosix
  25. import NIOSSL
  26. import NIOTransportServices
  27. import GRPCSampleData
  28. import Security
  29. import XCTest
  30. @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
  31. final class GRPCNetworkFrameworkTests: GRPCTestCase {
  32. private var server: Server!
  33. private var client: ClientConnection!
  34. private var identity: SecIdentity!
  35. private var pkcs12Bundle: NIOSSLPKCS12Bundle!
  36. private var tsGroup: NIOTSEventLoopGroup!
  37. private var group: MultiThreadedEventLoopGroup!
  38. private let queue = DispatchQueue(label: "io.grpc.verify-handshake")
  39. private static let p12bundleURL = URL(fileURLWithPath: #filePath)
  40. .deletingLastPathComponent() // (this file)
  41. .deletingLastPathComponent() // GRPCTests
  42. .deletingLastPathComponent() // Tests
  43. .appendingPathComponent("Sources")
  44. .appendingPathComponent("GRPCSampleData")
  45. .appendingPathComponent("bundle")
  46. .appendingPathExtension("p12")
  47. // Not really 'async' but there is no 'func setUp() throws' to override.
  48. override func setUp() async throws {
  49. try await super.setUp()
  50. self.tsGroup = NIOTSEventLoopGroup(loopCount: 1)
  51. self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
  52. self.identity = try self.loadIdentity()
  53. XCTAssertNotNil(
  54. self.identity,
  55. "Unable to load identity from '\(GRPCNetworkFrameworkTests.p12bundleURL)'"
  56. )
  57. self.pkcs12Bundle = try NIOSSLPKCS12Bundle(
  58. file: GRPCNetworkFrameworkTests.p12bundleURL.path,
  59. passphrase: "password".utf8
  60. )
  61. XCTAssertNotNil(
  62. self.pkcs12Bundle,
  63. "Unable to load PCKS12 bundle from '\(GRPCNetworkFrameworkTests.p12bundleURL)'"
  64. )
  65. }
  66. override func tearDown() {
  67. XCTAssertNoThrow(try self.client?.close().wait())
  68. XCTAssertNoThrow(try self.server?.close().wait())
  69. XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
  70. XCTAssertNoThrow(try self.tsGroup?.syncShutdownGracefully())
  71. super.tearDown()
  72. }
  73. private func loadIdentity() throws -> SecIdentity? {
  74. let data = try Data(contentsOf: GRPCNetworkFrameworkTests.p12bundleURL)
  75. let options = [kSecImportExportPassphrase as String: "password"]
  76. var rawItems: CFArray?
  77. let status = SecPKCS12Import(data as CFData, options as CFDictionary, &rawItems)
  78. switch status {
  79. case errSecSuccess:
  80. ()
  81. case errSecInteractionNotAllowed:
  82. throw XCTSkip("Unable to import PKCS12 bundle: no interaction allowed")
  83. default:
  84. XCTFail("SecPKCS12Import: failed with status \(status)")
  85. return nil
  86. }
  87. let items = rawItems! as! [[String: Any]]
  88. return items.first?[kSecImportItemIdentity as String] as! SecIdentity?
  89. }
  90. private func doEchoGet() throws {
  91. let echo = Echo_EchoNIOClient(channel: self.client)
  92. let get = echo.get(.with { $0.text = "hello" })
  93. XCTAssertNoThrow(try get.response.wait())
  94. }
  95. private func startServer(_ builder: Server.Builder) throws {
  96. self.server =
  97. try builder
  98. .withServiceProviders([EchoProvider()])
  99. .withLogger(self.serverLogger)
  100. .bind(host: "127.0.0.1", port: 0)
  101. .wait()
  102. }
  103. private func startClient(_ builder: ClientConnection.Builder) {
  104. self.client =
  105. builder
  106. .withBackgroundActivityLogger(self.clientLogger)
  107. .withConnectionReestablishment(enabled: false)
  108. .connect(host: "127.0.0.1", port: self.server.channel.localAddress!.port!)
  109. }
  110. func testNetworkFrameworkServerWithNIOSSLClient() throws {
  111. let serverBuilder = Server.usingTLSBackedByNetworkFramework(
  112. on: self.tsGroup,
  113. with: self.identity
  114. )
  115. XCTAssertNoThrow(try self.startServer(serverBuilder))
  116. let clientBuilder = ClientConnection.usingTLSBackedByNIOSSL(on: self.group)
  117. .withTLS(serverHostnameOverride: "localhost")
  118. .withTLS(trustRoots: .certificates(self.pkcs12Bundle.certificateChain))
  119. self.startClient(clientBuilder)
  120. XCTAssertNoThrow(try self.doEchoGet())
  121. }
  122. func testNIOSSLServerOnMTELGWithNetworkFrameworkClient() throws {
  123. try self.doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: self.group)
  124. }
  125. func testNIOSSLServerOnNIOTSGroupWithNetworkFrameworkClient() throws {
  126. try self.doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: self.tsGroup)
  127. }
  128. func doTestNIOSSLServerWithNetworkFrameworkClient(serverGroup: EventLoopGroup) throws {
  129. let serverBuilder = Server.usingTLSBackedByNIOSSL(
  130. on: serverGroup,
  131. certificateChain: self.pkcs12Bundle.certificateChain,
  132. privateKey: self.pkcs12Bundle.privateKey
  133. )
  134. XCTAssertNoThrow(try self.startServer(serverBuilder))
  135. var certificate: SecCertificate?
  136. guard SecIdentityCopyCertificate(self.identity, &certificate) == errSecSuccess else {
  137. XCTFail("Unable to extract certificate from identity")
  138. return
  139. }
  140. let clientBuilder = ClientConnection.usingTLSBackedByNetworkFramework(on: self.tsGroup)
  141. .withTLS(serverHostnameOverride: "localhost")
  142. .withTLSHandshakeVerificationCallback(on: self.queue) { _, trust, verify in
  143. let actualTrust = sec_trust_copy_ref(trust).takeRetainedValue()
  144. SecTrustSetAnchorCertificates(actualTrust, [certificate!] as CFArray)
  145. SecTrustEvaluateAsyncWithError(actualTrust, self.queue) { _, valid, error in
  146. if let error = error {
  147. XCTFail("Trust evaluation error: \(error)")
  148. }
  149. verify(valid)
  150. }
  151. }
  152. self.startClient(clientBuilder)
  153. XCTAssertNoThrow(try self.doEchoGet())
  154. }
  155. func testNetworkFrameworkTLServerAndClient() throws {
  156. let serverBuilder = Server.usingTLSBackedByNetworkFramework(
  157. on: self.tsGroup,
  158. with: self.identity
  159. )
  160. XCTAssertNoThrow(try self.startServer(serverBuilder))
  161. var certificate: SecCertificate?
  162. guard SecIdentityCopyCertificate(self.identity, &certificate) == errSecSuccess else {
  163. XCTFail("Unable to extract certificate from identity")
  164. return
  165. }
  166. let clientBuilder = ClientConnection.usingTLSBackedByNetworkFramework(on: self.tsGroup)
  167. .withTLS(serverHostnameOverride: "localhost")
  168. .withTLSHandshakeVerificationCallback(on: self.queue) { _, trust, verify in
  169. let actualTrust = sec_trust_copy_ref(trust).takeRetainedValue()
  170. SecTrustSetAnchorCertificates(actualTrust, [certificate!] as CFArray)
  171. SecTrustEvaluateAsyncWithError(actualTrust, self.queue) { _, valid, error in
  172. if let error = error {
  173. XCTFail("Trust evaluation error: \(error)")
  174. }
  175. verify(valid)
  176. }
  177. }
  178. self.startClient(clientBuilder)
  179. XCTAssertNoThrow(try self.doEchoGet())
  180. }
  181. func testWaiterPicksUpNWError(
  182. _ configure: (inout GRPCChannelPool.Configuration) -> Void
  183. ) async throws {
  184. let builder = Server.usingTLSBackedByNIOSSL(
  185. on: self.group,
  186. certificateChain: [SampleCertificate.server.certificate],
  187. privateKey: SamplePrivateKey.server
  188. )
  189. let server = try await builder.bind(host: "127.0.0.1", port: 0).get()
  190. defer { try? server.close().wait() }
  191. let client = try GRPCChannelPool.with(
  192. target: .hostAndPort("127.0.0.1", server.channel.localAddress!.port!),
  193. transportSecurity: .tls(.makeClientConfigurationBackedByNetworkFramework()),
  194. eventLoopGroup: self.tsGroup
  195. ) {
  196. configure(&$0)
  197. }
  198. let echo = Echo_EchoAsyncClient(channel: client)
  199. do {
  200. let _ = try await echo.get(.with { $0.text = "ignored" })
  201. } catch let error as GRPCConnectionPoolError {
  202. XCTAssertEqual(error.code, .deadlineExceeded)
  203. XCTAssert(error.underlyingError is NWError)
  204. } catch {
  205. XCTFail("Expected GRPCConnectionPoolError")
  206. }
  207. let promise = self.group.next().makePromise(of: Void.self)
  208. client.closeGracefully(deadline: .now() + .seconds(1), promise: promise)
  209. try await promise.futureResult.get()
  210. }
  211. func testErrorPickedUpBeforeConnectTimeout() async throws {
  212. try await self.testWaiterPicksUpNWError {
  213. // Configure the wait time to be less than the connect timeout, the waiter
  214. // should fail with the appropriate NWError before the connect times out.
  215. $0.connectionPool.maxWaitTime = .milliseconds(500)
  216. $0.connectionBackoff.minimumConnectionTimeout = 1.0
  217. }
  218. }
  219. func testNotWaitingForConnectivity() async throws {
  220. try await self.testWaiterPicksUpNWError {
  221. // The minimum connect time is still high, but setting wait for activity to false
  222. // means it fails on entering the waiting state rather than seeing out the connect
  223. // timeout.
  224. $0.connectionPool.maxWaitTime = .milliseconds(500)
  225. $0.debugChannelInitializer = { channel in
  226. channel.setOption(NIOTSChannelOptions.waitForActivity, value: false)
  227. }
  228. }
  229. }
  230. }
  231. #endif // canImport(Network)
  232. #endif // canImport(NIOSSL)