/src/swift-nio/Sources/NIOPosix/RawSocketBootstrap.swift
Line | Count | Source |
1 | | //===----------------------------------------------------------------------===// |
2 | | // |
3 | | // This source file is part of the SwiftNIO open source project |
4 | | // |
5 | | // Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors |
6 | | // Licensed under Apache License v2.0 |
7 | | // |
8 | | // See LICENSE.txt for license information |
9 | | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors |
10 | | // |
11 | | // SPDX-License-Identifier: Apache-2.0 |
12 | | // |
13 | | //===----------------------------------------------------------------------===// |
14 | | |
15 | | #if !os(WASI) |
16 | | |
17 | | import NIOCore |
18 | | |
19 | | /// A `RawSocketBootstrap` is an easy way to interact with IP based protocols other then TCP and UDP. |
20 | | /// |
21 | | /// Example: |
22 | | /// |
23 | | /// ```swift |
24 | | /// let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) |
25 | | /// defer { |
26 | | /// try! group.syncShutdownGracefully() |
27 | | /// } |
28 | | /// let bootstrap = RawSocketBootstrap(group: group) |
29 | | /// .channelInitializer { channel in |
30 | | /// channel.pipeline.addHandler(MyChannelHandler()) |
31 | | /// } |
32 | | /// let channel = try! bootstrap.bind(host: "127.0.0.1", ipProtocol: .icmp).wait() |
33 | | /// /* the Channel is now ready to send/receive IP packets */ |
34 | | /// |
35 | | /// try channel.closeFuture.wait() // Wait until the channel un-binds. |
36 | | /// ``` |
37 | | /// |
38 | | /// The `Channel` will operate on `AddressedEnvelope<ByteBuffer>` as inbound and outbound messages. |
39 | | public final class NIORawSocketBootstrap { |
40 | | |
41 | | private let group: EventLoopGroup |
42 | | private var channelInitializer: Optional<ChannelInitializerCallback> |
43 | | @usableFromInline |
44 | | internal var _channelOptions: ChannelOptions.Storage |
45 | | |
46 | | /// Create a `RawSocketBootstrap` on the `EventLoopGroup` `group`. |
47 | | /// |
48 | | /// The `EventLoopGroup` `group` must be compatible, otherwise the program will crash. `RawSocketBootstrap` is |
49 | | /// compatible only with `MultiThreadedEventLoopGroup` as well as the `EventLoop`s returned by |
50 | | /// `MultiThreadedEventLoopGroup.next`. See `init(validatingGroup:)` for a fallible initializer for |
51 | | /// situations where it's impossible to tell ahead of time if the `EventLoopGroup` is compatible or not. |
52 | | /// |
53 | | /// - Parameters: |
54 | | /// - group: The `EventLoopGroup` to use. |
55 | 0 | public convenience init(group: EventLoopGroup) { |
56 | 0 | guard NIOOnSocketsBootstraps.isCompatible(group: group) else { |
57 | 0 | preconditionFailure( |
58 | 0 | "RawSocketBootstrap is only compatible with MultiThreadedEventLoopGroup and " |
59 | 0 | + "SelectableEventLoop. You tried constructing one with \(group) which is incompatible." |
60 | 0 | ) |
61 | 0 | } |
62 | 0 | self.init(validatingGroup: group)! |
63 | 0 | } |
64 | | |
65 | | /// Create a `RawSocketBootstrap` on the `EventLoopGroup` `group`, validating that `group` is compatible. |
66 | | /// |
67 | | /// - Parameters: |
68 | | /// - group: The `EventLoopGroup` to use. |
69 | 0 | public init?(validatingGroup group: EventLoopGroup) { |
70 | 0 | guard NIOOnSocketsBootstraps.isCompatible(group: group) else { |
71 | 0 | return nil |
72 | 0 | } |
73 | 0 | self._channelOptions = ChannelOptions.Storage() |
74 | 0 | self.group = group |
75 | 0 | self.channelInitializer = nil |
76 | 0 | } |
77 | | |
78 | | /// Initialize the bound `Channel` with `initializer`. The most common task in initializer is to add |
79 | | /// `ChannelHandler`s to the `ChannelPipeline`. |
80 | | /// |
81 | | /// - Parameters: |
82 | | /// - handler: A closure that initializes the provided `Channel`. |
83 | 0 | public func channelInitializer(_ handler: @escaping @Sendable (Channel) -> EventLoopFuture<Void>) -> Self { |
84 | 0 | self.channelInitializer = handler |
85 | 0 | return self |
86 | 0 | } |
87 | | |
88 | | /// Specifies a `ChannelOption` to be applied to the `Channel`. |
89 | | /// |
90 | | /// - Parameters: |
91 | | /// - option: The option to be applied. |
92 | | /// - value: The value for the option. |
93 | | @inlinable |
94 | 0 | public func channelOption<Option: ChannelOption>(_ option: Option, value: Option.Value) -> Self { |
95 | 0 | self._channelOptions.append(key: option, value: value) |
96 | 0 | return self |
97 | 0 | } |
98 | | |
99 | | /// Bind the `Channel` to `host`. |
100 | | /// All packets or errors matching the `ipProtocol` specified are passed to the resulting `Channel`. |
101 | | /// |
102 | | /// - Parameters: |
103 | | /// - host: The host to bind on. |
104 | | /// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field. |
105 | 0 | public func bind(host: String, ipProtocol: NIOIPProtocol) -> EventLoopFuture<Channel> { |
106 | 0 | bind0(ipProtocol: ipProtocol) { |
107 | 0 | try SocketAddress.makeAddressResolvingHost(host, port: 0) |
108 | 0 | } |
109 | 0 | } |
110 | | |
111 | | private func bind0( |
112 | | ipProtocol: NIOIPProtocol, |
113 | | _ makeSocketAddress: () throws -> SocketAddress |
114 | 0 | ) -> EventLoopFuture<Channel> { |
115 | 0 | let address: SocketAddress |
116 | 0 | do { |
117 | 0 | address = try makeSocketAddress() |
118 | 0 | } catch { |
119 | 0 | return group.next().makeFailedFuture(error) |
120 | 0 | } |
121 | 0 | precondition(address.port == nil || address.port == 0, "port must be 0 or not set") |
122 | 0 | func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel { |
123 | 0 | try DatagramChannel( |
124 | 0 | eventLoop: eventLoop, |
125 | 0 | protocolFamily: address.protocol, |
126 | 0 | protocolSubtype: .init(ipProtocol), |
127 | 0 | socketType: .raw |
128 | 0 | ) |
129 | 0 | } |
130 | 0 | return withNewChannel(makeChannel: makeChannel) { (eventLoop, channel) in |
131 | 0 | channel.register().flatMap { |
132 | 0 | channel.bind(to: address) |
133 | 0 | } |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | | /// Connect the `Channel` to `host`. |
138 | | /// |
139 | | /// - Parameters: |
140 | | /// - host: The host to connect to. |
141 | | /// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field. |
142 | 0 | public func connect(host: String, ipProtocol: NIOIPProtocol) -> EventLoopFuture<Channel> { |
143 | 0 | connect0(ipProtocol: ipProtocol) { |
144 | 0 | try SocketAddress.makeAddressResolvingHost(host, port: 0) |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | private func connect0( |
149 | | ipProtocol: NIOIPProtocol, |
150 | | _ makeSocketAddress: () throws -> SocketAddress |
151 | 0 | ) -> EventLoopFuture<Channel> { |
152 | 0 | let address: SocketAddress |
153 | 0 | do { |
154 | 0 | address = try makeSocketAddress() |
155 | 0 | } catch { |
156 | 0 | return group.next().makeFailedFuture(error) |
157 | 0 | } |
158 | 0 | func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel { |
159 | 0 | try DatagramChannel( |
160 | 0 | eventLoop: eventLoop, |
161 | 0 | protocolFamily: address.protocol, |
162 | 0 | protocolSubtype: .init(ipProtocol), |
163 | 0 | socketType: .raw |
164 | 0 | ) |
165 | 0 | } |
166 | 0 | return withNewChannel(makeChannel: makeChannel) { (eventLoop, channel) in |
167 | 0 | channel.register().flatMap { |
168 | 0 | channel.connect(to: address) |
169 | 0 | } |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | | private func withNewChannel( |
174 | | makeChannel: (_ eventLoop: SelectableEventLoop) throws -> DatagramChannel, |
175 | | _ bringup: @escaping @Sendable (EventLoop, DatagramChannel) -> EventLoopFuture<Void> |
176 | 0 | ) -> EventLoopFuture<Channel> { |
177 | 0 | let eventLoop = self.group.next() |
178 | 0 | let channelInitializer = self.channelInitializer ?? { @Sendable _ in eventLoop.makeSucceededFuture(()) }Unexecuted instantiation: $s8NIOPosix21NIORawSocketBootstrapC14withNewChannel33_05F8667EF5648375E7110E8ED8CF5F24LL04makeG0_7NIOCore15EventLoopFutureCyAG0G0_pGAA08DatagramG0CAA010SelectableqR0CKXE_AIyytGAG0qR0_p_AMtYbctFApgJ_pYbcyKXEfu_ Unexecuted instantiation: $s8NIOPosix21NIORawSocketBootstrapC14withNewChannel33_05F8667EF5648375E7110E8ED8CF5F24LL04makeG0_7NIOCore15EventLoopFutureCyAG0G0_pGAA08DatagramG0CAA010SelectableqR0CKXE_AIyytGAG0qR0_p_AMtYbctFApgJ_pYbcyKXEfu_ApgJ_pYbcfU_ |
179 | 0 | let channelOptions = self._channelOptions |
180 | 0 |
|
181 | 0 | let channel: DatagramChannel |
182 | 0 | do { |
183 | 0 | channel = try makeChannel(eventLoop as! SelectableEventLoop) |
184 | 0 | } catch { |
185 | 0 | return eventLoop.makeFailedFuture(error) |
186 | 0 | } |
187 | 0 |
|
188 | 0 | @Sendable |
189 | 0 | func setupChannel() -> EventLoopFuture<Channel> { |
190 | 0 | eventLoop.assertInEventLoop() |
191 | 0 | return channelOptions.applyAllChannelOptions(to: channel).flatMap { |
192 | 0 | channelInitializer(channel) |
193 | 0 | }.flatMap { |
194 | 0 | eventLoop.assertInEventLoop() |
195 | 0 | return bringup(eventLoop, channel) |
196 | 0 | }.map { |
197 | 0 | channel |
198 | 0 | }.flatMapError { error in |
199 | 0 | eventLoop.makeFailedFuture(error) |
200 | 0 | } |
201 | 0 | } |
202 | 0 |
|
203 | 0 | if eventLoop.inEventLoop { |
204 | 0 | return setupChannel() |
205 | 0 | } else { |
206 | 0 | return eventLoop.flatSubmit { |
207 | 0 | setupChannel() |
208 | 0 | } |
209 | 0 | } |
210 | 0 | } |
211 | | } |
212 | | |
213 | | // MARK: Async connect/bind methods |
214 | | |
215 | | extension NIORawSocketBootstrap { |
216 | | /// Bind the `Channel` to `host`. |
217 | | /// All packets or errors matching the `ipProtocol` specified are passed to the resulting `Channel`. |
218 | | /// |
219 | | /// - Parameters: |
220 | | /// - host: The host to bind on. |
221 | | /// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field. |
222 | | /// - channelInitializer: A closure to initialize the channel. The return value of this closure is returned from the `bind` |
223 | | /// method. |
224 | | /// - Returns: The result of the channel initializer. |
225 | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
226 | | public func bind<Output: Sendable>( |
227 | | host: String, |
228 | | ipProtocol: NIOIPProtocol, |
229 | | channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Output> |
230 | 0 | ) async throws -> Output { |
231 | 0 | try await self.bind0( |
232 | 0 | host: host, |
233 | 0 | ipProtocol: ipProtocol, |
234 | 0 | channelInitializer: channelInitializer, |
235 | 0 | postRegisterTransformation: { $1.makeSucceededFuture($0) } |
236 | 0 | ) |
237 | 0 | } |
238 | | |
239 | | /// Connect the `Channel` to `host`. |
240 | | /// |
241 | | /// - Parameters: |
242 | | /// - host: The host to connect to. |
243 | | /// - ipProtocol: The IP protocol used in the IP protocol/nextHeader field. |
244 | | /// - channelInitializer: A closure to initialize the channel. The return value of this closure is returned from the `connect` |
245 | | /// method. |
246 | | /// - Returns: The result of the channel initializer. |
247 | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
248 | | public func connect<Output: Sendable>( |
249 | | host: String, |
250 | | ipProtocol: NIOIPProtocol, |
251 | | channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<Output> |
252 | 0 | ) async throws -> Output { |
253 | 0 | try await self.connect0( |
254 | 0 | host: host, |
255 | 0 | ipProtocol: ipProtocol, |
256 | 0 | channelInitializer: channelInitializer, |
257 | 0 | postRegisterTransformation: { $1.makeSucceededFuture($0) } |
258 | 0 | ) |
259 | 0 | } |
260 | | |
261 | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
262 | | private func connect0<ChannelInitializerResult: Sendable, PostRegistrationTransformationResult: Sendable>( |
263 | | host: String, |
264 | | ipProtocol: NIOIPProtocol, |
265 | | channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<ChannelInitializerResult>, |
266 | | postRegisterTransformation: |
267 | | @escaping @Sendable (ChannelInitializerResult, EventLoop) -> EventLoopFuture< |
268 | | PostRegistrationTransformationResult |
269 | | > |
270 | 0 | ) async throws -> PostRegistrationTransformationResult { |
271 | 0 | let address = try SocketAddress.makeAddressResolvingHost(host, port: 0) |
272 | 0 |
|
273 | 0 | func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel { |
274 | 0 | try DatagramChannel( |
275 | 0 | eventLoop: eventLoop, |
276 | 0 | protocolFamily: address.protocol, |
277 | 0 | protocolSubtype: .init(ipProtocol), |
278 | 0 | socketType: .raw |
279 | 0 | ) |
280 | 0 | } |
281 | 0 |
|
282 | 0 | return try await self.makeConfiguredChannel( |
283 | 0 | makeChannel: makeChannel(_:), |
284 | 0 | channelInitializer: channelInitializer, |
285 | 0 | registration: { channel in |
286 | 0 | channel.register().flatMap { |
287 | 0 | channel.connect(to: address) |
288 | 0 | } |
289 | 0 | }, |
290 | 0 | postRegisterTransformation: postRegisterTransformation |
291 | 0 | ).get() |
292 | 0 | } |
293 | | |
294 | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
295 | | private func bind0<ChannelInitializerResult: Sendable, PostRegistrationTransformationResult: Sendable>( |
296 | | host: String, |
297 | | ipProtocol: NIOIPProtocol, |
298 | | channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<ChannelInitializerResult>, |
299 | | postRegisterTransformation: |
300 | | @escaping @Sendable (ChannelInitializerResult, EventLoop) -> EventLoopFuture< |
301 | | PostRegistrationTransformationResult |
302 | | > |
303 | 0 | ) async throws -> PostRegistrationTransformationResult { |
304 | 0 | let address = try SocketAddress.makeAddressResolvingHost(host, port: 0) |
305 | 0 |
|
306 | 0 | precondition(address.port == nil || address.port == 0, "port must be 0 or not set") |
307 | 0 | func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel { |
308 | 0 | try DatagramChannel( |
309 | 0 | eventLoop: eventLoop, |
310 | 0 | protocolFamily: address.protocol, |
311 | 0 | protocolSubtype: .init(ipProtocol), |
312 | 0 | socketType: .raw |
313 | 0 | ) |
314 | 0 | } |
315 | 0 |
|
316 | 0 | return try await self.makeConfiguredChannel( |
317 | 0 | makeChannel: makeChannel(_:), |
318 | 0 | channelInitializer: channelInitializer, |
319 | 0 | registration: { channel in |
320 | 0 | channel.register().flatMap { |
321 | 0 | channel.bind(to: address) |
322 | 0 | } |
323 | 0 | }, |
324 | 0 | postRegisterTransformation: postRegisterTransformation |
325 | 0 | ).get() |
326 | 0 | } |
327 | | |
328 | | @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) |
329 | | private func makeConfiguredChannel< |
330 | | ChannelInitializerResult: Sendable, |
331 | | PostRegistrationTransformationResult: Sendable |
332 | | >( |
333 | | makeChannel: (_ eventLoop: SelectableEventLoop) throws -> DatagramChannel, |
334 | | channelInitializer: @escaping @Sendable (Channel) -> EventLoopFuture<ChannelInitializerResult>, |
335 | | registration: @escaping @Sendable (Channel) -> EventLoopFuture<Void>, |
336 | | postRegisterTransformation: |
337 | | @escaping @Sendable (ChannelInitializerResult, EventLoop) -> EventLoopFuture< |
338 | | PostRegistrationTransformationResult |
339 | | > |
340 | 0 | ) -> EventLoopFuture<PostRegistrationTransformationResult> { |
341 | 0 | let eventLoop = self.group.next() |
342 | 0 | let bootstrapInitializer = self.channelInitializer ?? { @Sendable _ in eventLoop.makeSucceededFuture(()) }Unexecuted instantiation: $s8NIOPosix21NIORawSocketBootstrapC21makeConfiguredChannel33_05F8667EF5648375E7110E8ED8CF5F24LL0eG018channelInitializer12registration26postRegisterTransformation7NIOCore15EventLoopFutureCyq_GAA08DatagramG0CAA010SelectablevW0CKXE_ALyxGAJ0G0_pYbcALyytGAjS_pYbcAMx_AJ0vW0_ptYbcts8SendableRzsAVR_r0_lFAtjS_pYbcyKXEfu_ Unexecuted instantiation: $s8NIOPosix21NIORawSocketBootstrapC21makeConfiguredChannel33_05F8667EF5648375E7110E8ED8CF5F24LL0eG018channelInitializer12registration26postRegisterTransformation7NIOCore15EventLoopFutureCyq_GAA08DatagramG0CAA010SelectablevW0CKXE_ALyxGAJ0G0_pYbcALyytGAjS_pYbcAMx_AJ0vW0_ptYbcts8SendableRzsAVR_r0_lFAtjS_pYbcyKXEfu_AtjS_pYbcfU_ |
343 | 0 | let channelInitializer = { @Sendable (channel: Channel) -> EventLoopFuture<ChannelInitializerResult> in |
344 | 0 | bootstrapInitializer(channel).flatMap { channelInitializer(channel) } |
345 | 0 | } |
346 | 0 | let channelOptions = self._channelOptions |
347 | 0 |
|
348 | 0 | let channel: DatagramChannel |
349 | 0 | do { |
350 | 0 | channel = try makeChannel(eventLoop as! SelectableEventLoop) |
351 | 0 | } catch { |
352 | 0 | return eventLoop.makeFailedFuture(error) |
353 | 0 | } |
354 | 0 |
|
355 | 0 | @Sendable |
356 | 0 | func setupChannel() -> EventLoopFuture<PostRegistrationTransformationResult> { |
357 | 0 | eventLoop.assertInEventLoop() |
358 | 0 | return channelOptions.applyAllChannelOptions(to: channel).flatMap { |
359 | 0 | channelInitializer(channel) |
360 | 0 | }.flatMap { (result: ChannelInitializerResult) in |
361 | 0 | eventLoop.assertInEventLoop() |
362 | 0 | return registration(channel).map { |
363 | 0 | result |
364 | 0 | } |
365 | 0 | }.flatMap { (result: ChannelInitializerResult) -> EventLoopFuture<PostRegistrationTransformationResult> in |
366 | 0 | postRegisterTransformation(result, eventLoop) |
367 | 0 | }.flatMapError { error in |
368 | 0 | eventLoop.assertInEventLoop() |
369 | 0 | channel.close0(error: error, mode: .all, promise: nil) |
370 | 0 | return channel.eventLoop.makeFailedFuture(error) |
371 | 0 | } |
372 | 0 | } |
373 | 0 |
|
374 | 0 | if eventLoop.inEventLoop { |
375 | 0 | return setupChannel() |
376 | 0 | } else { |
377 | 0 | return eventLoop.flatSubmit { |
378 | 0 | setupChannel() |
379 | 0 | } |
380 | 0 | } |
381 | 0 | } |
382 | | } |
383 | | |
384 | | @available(*, unavailable) |
385 | | extension NIORawSocketBootstrap: Sendable {} |
386 | | #endif // !os(WASI) |