ConnectionManagerChannelProvider.swift 10 KB

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