ConnectionManagerChannelProvider.swift 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. import Logging
  17. import NIOCore
  18. import NIOPosix
  19. import NIOTransportServices
  20. #if canImport(NIOSSL)
  21. import NIOSSL
  22. #endif
  23. #if canImport(Network)
  24. import Network
  25. #endif
  26. @usableFromInline
  27. internal protocol ConnectionManagerChannelProvider {
  28. /// Make an `EventLoopFuture<Channel>`.
  29. ///
  30. /// - Parameters:
  31. /// - connectionManager: The `ConnectionManager` requesting the `Channel`.
  32. /// - eventLoop: The `EventLoop` to use for the`Channel`.
  33. /// - connectTimeout: Optional connection timeout when starting the connection.
  34. /// - logger: A logger.
  35. func makeChannel(
  36. managedBy connectionManager: ConnectionManager,
  37. onEventLoop eventLoop: EventLoop,
  38. connectTimeout: TimeAmount?,
  39. logger: Logger
  40. ) -> EventLoopFuture<Channel>
  41. }
  42. @usableFromInline
  43. internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
  44. @usableFromInline
  45. enum TLSMode {
  46. #if canImport(NIOSSL)
  47. case configureWithNIOSSL(Result<NIOSSLContext, Error>)
  48. #endif // canImport(NIOSSL)
  49. case configureWithNetworkFramework
  50. case disabled
  51. }
  52. @usableFromInline
  53. internal var connectionTarget: ConnectionTarget
  54. @usableFromInline
  55. internal var connectionKeepalive: ClientConnectionKeepalive
  56. @usableFromInline
  57. internal var connectionIdleTimeout: TimeAmount
  58. @usableFromInline
  59. internal var tlsMode: TLSMode
  60. @usableFromInline
  61. internal var tlsConfiguration: GRPCTLSConfiguration?
  62. @usableFromInline
  63. internal var httpTargetWindowSize: Int
  64. @usableFromInline
  65. internal var httpMaxFrameSize: Int
  66. @usableFromInline
  67. internal var errorDelegate: Optional<ClientErrorDelegate>
  68. @usableFromInline
  69. internal var debugChannelInitializer: Optional<(Channel) -> EventLoopFuture<Void>>
  70. #if canImport(Network)
  71. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  72. @usableFromInline
  73. internal var nwParametersConfigurator: (@Sendable (NWParameters) -> Void)? {
  74. get {
  75. self._nwParametersConfigurator as! (@Sendable (NWParameters) -> Void)?
  76. }
  77. set {
  78. self._nwParametersConfigurator = newValue
  79. }
  80. }
  81. private var _nwParametersConfigurator: (any Sendable)?
  82. #endif
  83. #if canImport(Network)
  84. @inlinable
  85. @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
  86. internal init(
  87. connectionTarget: ConnectionTarget,
  88. connectionKeepalive: ClientConnectionKeepalive,
  89. connectionIdleTimeout: TimeAmount,
  90. tlsMode: TLSMode,
  91. tlsConfiguration: GRPCTLSConfiguration?,
  92. httpTargetWindowSize: Int,
  93. httpMaxFrameSize: Int,
  94. errorDelegate: ClientErrorDelegate?,
  95. debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?,
  96. nwParametersConfigurator: (@Sendable (NWParameters) -> Void)?
  97. ) {
  98. self.init(
  99. connectionTarget: connectionTarget,
  100. connectionKeepalive: connectionKeepalive,
  101. connectionIdleTimeout: connectionIdleTimeout,
  102. tlsMode: tlsMode,
  103. tlsConfiguration: tlsConfiguration,
  104. httpTargetWindowSize: httpTargetWindowSize,
  105. httpMaxFrameSize: httpMaxFrameSize,
  106. errorDelegate: errorDelegate,
  107. debugChannelInitializer: debugChannelInitializer
  108. )
  109. self.nwParametersConfigurator = nwParametersConfigurator
  110. }
  111. #endif
  112. @inlinable
  113. internal init(
  114. connectionTarget: ConnectionTarget,
  115. connectionKeepalive: ClientConnectionKeepalive,
  116. connectionIdleTimeout: TimeAmount,
  117. tlsMode: TLSMode,
  118. tlsConfiguration: GRPCTLSConfiguration?,
  119. httpTargetWindowSize: Int,
  120. httpMaxFrameSize: Int,
  121. errorDelegate: ClientErrorDelegate?,
  122. debugChannelInitializer: ((Channel) -> EventLoopFuture<Void>)?
  123. ) {
  124. self.connectionTarget = connectionTarget
  125. self.connectionKeepalive = connectionKeepalive
  126. self.connectionIdleTimeout = connectionIdleTimeout
  127. self.tlsMode = tlsMode
  128. self.tlsConfiguration = tlsConfiguration
  129. self.httpTargetWindowSize = httpTargetWindowSize
  130. self.httpMaxFrameSize = httpMaxFrameSize
  131. self.errorDelegate = errorDelegate
  132. self.debugChannelInitializer = debugChannelInitializer
  133. }
  134. internal init(configuration: ClientConnection.Configuration) {
  135. // Making a `NIOSSLContext` is expensive and we should only do it (at most) once per TLS
  136. // configuration. We do it now and store it in our `tlsMode` and surface any error during
  137. // channel creation (we're limited by our API in when we can throw any error).
  138. let tlsMode: TLSMode
  139. if let tlsConfiguration = configuration.tlsConfiguration {
  140. if tlsConfiguration.isNetworkFrameworkTLSBackend {
  141. tlsMode = .configureWithNetworkFramework
  142. } else {
  143. #if canImport(NIOSSL)
  144. // The '!' is okay here, we have a `tlsConfiguration` (so we must be using TLS) and we know
  145. // it's not backed by Network.framework, so it must be backed by NIOSSL.
  146. tlsMode = .configureWithNIOSSL(Result { try tlsConfiguration.makeNIOSSLContext()! })
  147. #else
  148. // TLS is configured, and we aren't using a Network.framework TLS backend, so we must be
  149. // using NIOSSL, so we must be able to import it.
  150. fatalError()
  151. #endif // canImport(NIOSSL)
  152. }
  153. } else {
  154. tlsMode = .disabled
  155. }
  156. self.init(
  157. connectionTarget: configuration.target,
  158. connectionKeepalive: configuration.connectionKeepalive,
  159. connectionIdleTimeout: configuration.connectionIdleTimeout,
  160. tlsMode: tlsMode,
  161. tlsConfiguration: configuration.tlsConfiguration,
  162. httpTargetWindowSize: configuration.httpTargetWindowSize,
  163. httpMaxFrameSize: configuration.httpMaxFrameSize,
  164. errorDelegate: configuration.errorDelegate,
  165. debugChannelInitializer: configuration.debugChannelInitializer
  166. )
  167. #if canImport(Network)
  168. if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *) {
  169. self.nwParametersConfigurator = configuration.nwParametersConfigurator
  170. }
  171. #endif
  172. }
  173. private var serverHostname: String? {
  174. let hostname = self.tlsConfiguration?.hostnameOverride ?? self.connectionTarget.host
  175. return hostname.isIPAddress ? nil : hostname
  176. }
  177. private var hasTLS: Bool {
  178. return self.tlsConfiguration != nil
  179. }
  180. private func requiresZeroLengthWorkaround(eventLoop: EventLoop) -> Bool {
  181. return PlatformSupport.requiresZeroLengthWriteWorkaround(group: eventLoop, hasTLS: self.hasTLS)
  182. }
  183. @usableFromInline
  184. internal func makeChannel(
  185. managedBy connectionManager: ConnectionManager,
  186. onEventLoop eventLoop: EventLoop,
  187. connectTimeout: TimeAmount?,
  188. logger: Logger
  189. ) -> EventLoopFuture<Channel> {
  190. let hostname = self.serverHostname
  191. let needsZeroLengthWriteWorkaround = self.requiresZeroLengthWorkaround(eventLoop: eventLoop)
  192. var bootstrap = PlatformSupport.makeClientBootstrap(
  193. group: eventLoop,
  194. tlsConfiguration: self.tlsConfiguration,
  195. logger: logger
  196. )
  197. bootstrap =
  198. bootstrap
  199. .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
  200. .channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
  201. .channelInitializer { channel in
  202. let sync = channel.pipeline.syncOperations
  203. do {
  204. if needsZeroLengthWriteWorkaround {
  205. try sync.addHandler(NIOFilterEmptyWritesHandler())
  206. }
  207. // We have a NIOSSL context to apply. If we're using TLS from NIOTS then the bootstrap
  208. // will already have the TLS options applied.
  209. switch self.tlsMode {
  210. #if canImport(NIOSSL)
  211. case let .configureWithNIOSSL(sslContext):
  212. try sync.configureNIOSSLForGRPCClient(
  213. sslContext: sslContext,
  214. serverHostname: hostname,
  215. customVerificationCallback: self.tlsConfiguration?.nioSSLCustomVerificationCallback,
  216. logger: logger
  217. )
  218. #endif // canImport(NIOSSL)
  219. // Network.framework TLS configuration is applied when creating the bootstrap so is a
  220. // no-op here.
  221. case .configureWithNetworkFramework,
  222. .disabled:
  223. ()
  224. }
  225. try sync.configureHTTP2AndGRPCHandlersForGRPCClient(
  226. channel: channel,
  227. connectionManager: connectionManager,
  228. connectionKeepalive: self.connectionKeepalive,
  229. connectionIdleTimeout: self.connectionIdleTimeout,
  230. httpTargetWindowSize: self.httpTargetWindowSize,
  231. httpMaxFrameSize: self.httpMaxFrameSize,
  232. errorDelegate: self.errorDelegate,
  233. logger: logger
  234. )
  235. } catch {
  236. return channel.eventLoop.makeFailedFuture(error)
  237. }
  238. // Run the debug initializer, if there is one.
  239. if let debugInitializer = self.debugChannelInitializer {
  240. return debugInitializer(channel)
  241. } else {
  242. return channel.eventLoop.makeSucceededVoidFuture()
  243. }
  244. }
  245. if let connectTimeout = connectTimeout {
  246. _ = bootstrap.connectTimeout(connectTimeout)
  247. }
  248. #if canImport(Network)
  249. if #available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *),
  250. let configurator = self.nwParametersConfigurator,
  251. let transportServicesBootstrap = bootstrap as? NIOTSConnectionBootstrap
  252. {
  253. _ = transportServicesBootstrap.configureNWParameters(configurator)
  254. }
  255. #endif
  256. return bootstrap.connect(to: self.connectionTarget)
  257. }
  258. }