ConnectionManagerChannelProvider.swift 10 KB

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