Skip to content

Commit 338cfb5

Browse files
vlmnormanmaurer
authored andcommitted
support for initializing off the system file descriptors (#285)
Motivation: In some contexts it is important to be able to pass file descriptors around instead of specifying the particular host and port. This allows removing certain windows for race conditions, and has positive security implications. Modifications: Added `withConnectedSocket(_:)` call to the `ClientBootstrap` that can be used to create a new `Channel` out of existing file descriptor representing the connected socket. Result: Allows initializing `Channel`s off the existing file descriptors.
1 parent 2bce766 commit 338cfb5

File tree

11 files changed

+254
-55
lines changed

11 files changed

+254
-55
lines changed

Sources/NIO/BaseSocketChannel.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,14 @@ class BaseSocketChannel<T: BaseSocket>: SelectableChannel, ChannelCore {
718718
}
719719
}
720720

721+
public final func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) {
722+
assert(self.eventLoop.inEventLoop)
723+
assert(self.isOpen)
724+
assert(!self.lifecycleManager.isActive)
725+
register0(promise: nil)
726+
becomeActive0(promise: promise)
727+
}
728+
721729
public final func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise<Void>?) {
722730
promise?.succeed(result: ())
723731
}

Sources/NIO/Bootstrap.swift

Lines changed: 126 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
/// A `ServerBootstrap` is an easy way to bootstrap a `ServerChannel` when creating network servers.
15+
/// A `ServerBootstrap` is an easy way to bootstrap a `ServerSocketChannel` when creating network servers.
1616
///
1717
/// Example:
1818
///
@@ -63,7 +63,7 @@ public final class ServerBootstrap {
6363
/// Create a `ServerBootstrap` for the `EventLoopGroup` `group`.
6464
///
6565
/// - parameters:
66-
/// - group: The `EventLoopGroup` to use for the `ServerChannel`.
66+
/// - group: The `EventLoopGroup` to use for the `ServerSocketChannel`.
6767
public convenience init(group: EventLoopGroup) {
6868
self.init(group: group, childGroup: group)
6969
}
@@ -132,12 +132,8 @@ public final class ServerBootstrap {
132132
/// - host: The host to bind on.
133133
/// - port: The port to bind on.
134134
public func bind(host: String, port: Int) -> EventLoopFuture<Channel> {
135-
let evGroup = group
136-
do {
137-
let address = try SocketAddress.newAddressResolving(host: host, port: port)
138-
return bind0(eventLoopGroup: evGroup, to: address)
139-
} catch let err {
140-
return evGroup.next().newFailedFuture(error: err)
135+
return bind0 {
136+
return try SocketAddress.newAddressResolving(host: host, port: port)
141137
}
142138
}
143139

@@ -146,54 +142,82 @@ public final class ServerBootstrap {
146142
/// - parameters:
147143
/// - address: The `SocketAddress` to bind on.
148144
public func bind(to address: SocketAddress) -> EventLoopFuture<Channel> {
149-
return bind0(eventLoopGroup: group, to: address)
145+
return bind0 { address }
150146
}
151147

152148
/// Bind the `ServerSocketChannel` to a UNIX Domain Socket.
153149
///
154150
/// - parameters:
155151
/// - unixDomainSocketPath: The _Unix domain socket_ path to bind to. `unixDomainSocketPath` must not exist, it will be created by the system.
156152
public func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel> {
157-
let evGroup = group
153+
return bind0 {
154+
try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
155+
}
156+
}
157+
158+
/// Use the existing bound socket file descriptor.
159+
///
160+
/// - parameters:
161+
/// - descriptor: The _Unix file descriptor_ representing the bound stream socket.
162+
public func withBoundSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
163+
func makeChannel(_ eventLoop: SelectableEventLoop, _ childEventLoopGroup: EventLoopGroup) throws -> ServerSocketChannel {
164+
return try ServerSocketChannel(descriptor: descriptor, eventLoop: eventLoop, group: childEventLoopGroup)
165+
}
166+
return bind0(makeServerChannel: makeChannel) { (eventLoop, serverChannel) in
167+
let promise: EventLoopPromise<Void> = eventLoop.newPromise()
168+
serverChannel.registerAlreadyConfigured0(promise: promise)
169+
return promise.futureResult
170+
}
171+
}
172+
173+
private func bind0(_ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
174+
let address: SocketAddress
158175
do {
159-
let address = try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
160-
return bind0(eventLoopGroup: evGroup, to: address)
161-
} catch let err {
162-
return evGroup.next().newFailedFuture(error: err)
176+
address = try makeSocketAddress()
177+
} catch {
178+
return group.next().newFailedFuture(error: error)
179+
}
180+
func makeChannel(_ eventLoop: SelectableEventLoop, _ childEventLoopGroup: EventLoopGroup) throws -> ServerSocketChannel {
181+
return try ServerSocketChannel(eventLoop: eventLoop,
182+
group: childEventLoopGroup,
183+
protocolFamily: address.protocolFamily)
184+
}
185+
return bind0(makeServerChannel: makeChannel) { (eventGroup, serverChannel) in
186+
serverChannel.registerAndDoSynchronously { serverChannel in
187+
serverChannel.bind(to: address)
188+
}
163189
}
164190
}
165191

166-
private func bind0(eventLoopGroup: EventLoopGroup, to address: SocketAddress) -> EventLoopFuture<Channel> {
192+
private func bind0(makeServerChannel: (_ eventLoop: SelectableEventLoop, _ childGroup: EventLoopGroup) throws -> ServerSocketChannel, _ register: @escaping (EventLoop, ServerSocketChannel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
193+
let eventLoop = self.group.next()
167194
let childEventLoopGroup = self.childGroup
168195
let serverChannelOptions = self.serverChannelOptions
169-
let eventLoop = eventLoopGroup.next()
170196
let serverChannelInit = self.serverChannelInit ?? { _ in eventLoop.newSucceededFuture(result: ()) }
171197
let childChannelInit = self.childChannelInit
172198
let childChannelOptions = self.childChannelOptions
173199

174-
let promise: EventLoopPromise<Channel> = eventLoop.newPromise()
200+
let future: EventLoopFuture<Channel>
175201
do {
176-
let serverChannel = try ServerSocketChannel(eventLoop: eventLoop as! SelectableEventLoop,
177-
group: childEventLoopGroup,
178-
protocolFamily: address.protocolFamily)
202+
let serverChannel = try makeServerChannel(eventLoop as! SelectableEventLoop, childEventLoopGroup)
179203

180-
serverChannelInit(serverChannel).then {
204+
future = serverChannelInit(serverChannel).then {
181205
serverChannel.pipeline.add(handler: AcceptHandler(childChannelInitializer: childChannelInit,
182206
childChannelOptions: childChannelOptions))
183207
}.then {
184208
serverChannelOptions.applyAll(channel: serverChannel)
185209
}.then {
186-
serverChannel.registerAndDoSynchronously { serverChannel in
187-
serverChannel.bind(to: address)
188-
}
210+
register(eventLoop, serverChannel)
189211
}.map {
190212
serverChannel
191-
}.cascade(promise: promise)
192-
} catch let err {
193-
promise.fail(error: err)
213+
}
214+
} catch {
215+
let promise: EventLoopPromise<Channel> = eventLoop.newPromise()
216+
promise.fail(error: error)
217+
future = promise.futureResult
194218
}
195219

196-
return promise.futureResult
220+
return future
197221
}
198222

199223
private class AcceptHandler: ChannelInboundHandler {
@@ -388,6 +412,31 @@ public final class ClientBootstrap {
388412
}
389413
}
390414

415+
/// Use the existing connected socket file descriptor.
416+
///
417+
/// - parameters:
418+
/// - descriptor: The _Unix file descriptor_ representing the connected stream socket.
419+
/// - returns: an `EventLoopFuture<Channel>` to deliver the `Channel` immediately.
420+
public func withConnectedSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
421+
let eventLoop = group.next()
422+
do {
423+
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.newSucceededFuture(result: ()) }
424+
let channel = try SocketChannel(eventLoop: eventLoop as! SelectableEventLoop, descriptor: descriptor)
425+
426+
return channelInitializer(channel).then {
427+
self.channelOptions.applyAll(channel: channel)
428+
}.then {
429+
let promise: EventLoopPromise<Void> = eventLoop.newPromise()
430+
channel.registerAlreadyConfigured0(promise: promise)
431+
return promise.futureResult
432+
}.map {
433+
channel
434+
}
435+
} catch {
436+
return eventLoop.newFailedFuture(error: error)
437+
}
438+
}
439+
391440
private func execute(eventLoop: EventLoop,
392441
protocolFamily: Int32,
393442
_ body: @escaping (Channel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
@@ -470,18 +519,29 @@ public final class DatagramBootstrap {
470519
return self
471520
}
472521

522+
/// Use the existing bound socket file descriptor.
523+
///
524+
/// - parameters:
525+
/// - descriptor: The _Unix file descriptor_ representing the bound datagram socket.
526+
public func withBoundSocket(descriptor: CInt) -> EventLoopFuture<Channel> {
527+
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
528+
return try DatagramChannel(eventLoop: eventLoop, descriptor: descriptor)
529+
}
530+
return bind0(makeChannel: makeChannel) { (eventLoop, channel) in
531+
let promise: EventLoopPromise<Void> = eventLoop.newPromise()
532+
channel.registerAlreadyConfigured0(promise: promise)
533+
return promise.futureResult
534+
}
535+
}
536+
473537
/// Bind the `DatagramChannel` to `host` and `port`.
474538
///
475539
/// - parameters:
476540
/// - host: The host to bind on.
477541
/// - port: The port to bind on.
478542
public func bind(host: String, port: Int) -> EventLoopFuture<Channel> {
479-
let evGroup = group
480-
do {
481-
let address = try SocketAddress.newAddressResolving(host: host, port: port)
482-
return bind0(eventLoopGroup: evGroup, to: address)
483-
} catch let err {
484-
return evGroup.next().newFailedFuture(error: err)
543+
return bind0 {
544+
return try SocketAddress.newAddressResolving(host: host, port: port)
485545
}
486546
}
487547

@@ -490,47 +550,60 @@ public final class DatagramBootstrap {
490550
/// - parameters:
491551
/// - address: The `SocketAddress` to bind on.
492552
public func bind(to address: SocketAddress) -> EventLoopFuture<Channel> {
493-
return bind0(eventLoopGroup: group, to: address)
553+
return bind0 { address }
494554
}
495555

496556
/// Bind the `DatagramChannel` to a UNIX Domain Socket.
497557
///
498558
/// - parameters:
499559
/// - unixDomainSocketPath: The path of the UNIX Domain Socket to bind on. `path` must not exist, it will be created by the system.
500560
public func bind(unixDomainSocketPath: String) -> EventLoopFuture<Channel> {
501-
let evGroup = group
561+
return bind0 {
562+
return try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
563+
}
564+
}
565+
566+
private func bind0(_ makeSocketAddress: () throws -> SocketAddress) -> EventLoopFuture<Channel> {
567+
let address: SocketAddress
502568
do {
503-
let address = try SocketAddress(unixDomainSocketPath: unixDomainSocketPath)
504-
return bind0(eventLoopGroup: evGroup, to: address)
505-
} catch let err {
506-
return evGroup.next().newFailedFuture(error: err)
569+
address = try makeSocketAddress()
570+
} catch {
571+
return group.next().newFailedFuture(error: error)
572+
}
573+
func makeChannel(_ eventLoop: SelectableEventLoop) throws -> DatagramChannel {
574+
return try DatagramChannel(eventLoop: eventLoop,
575+
protocolFamily: address.protocolFamily)
576+
}
577+
return bind0(makeChannel: makeChannel) { (eventLoop, channel) in
578+
channel.register().then {
579+
_ in return channel.bind(to: address)
580+
}
507581
}
508582
}
509583

510-
private func bind0(eventLoopGroup: EventLoopGroup, to address: SocketAddress) -> EventLoopFuture<Channel> {
511-
let eventLoop = eventLoopGroup.next()
584+
private func bind0(makeChannel: (_ eventLoop: SelectableEventLoop) throws -> DatagramChannel, _ registerAndBind: @escaping (EventLoop, DatagramChannel) -> EventLoopFuture<Void>) -> EventLoopFuture<Channel> {
585+
let eventLoop = self.group.next()
512586
let channelInitializer = self.channelInitializer ?? { _ in eventLoop.newSucceededFuture(result: ()) }
513587
let channelOptions = self.channelOptions
514588

515-
let promise: EventLoopPromise<Channel> = eventLoop.newPromise()
589+
let future: EventLoopFuture<Channel>
516590
do {
517-
let channel = try DatagramChannel(eventLoop: eventLoop as! SelectableEventLoop,
518-
protocolFamily: address.protocolFamily)
591+
let channel = try makeChannel(eventLoop as! SelectableEventLoop)
519592

520-
channelInitializer(channel).then {
593+
future = channelInitializer(channel).then {
521594
channelOptions.applyAll(channel: channel)
522595
}.then {
523-
channel.register()
524-
}.then {
525-
channel.bind(to: address)
596+
registerAndBind(eventLoop, channel)
526597
}.map {
527598
channel
528-
}.cascade(promise: promise)
529-
} catch let err {
530-
promise.fail(error: err)
599+
}
600+
} catch {
601+
let promise: EventLoopPromise<Channel> = eventLoop.newPromise()
602+
promise.fail(error: error)
603+
future = promise.futureResult
531604
}
532605

533-
return promise.futureResult
606+
return future
534607
}
535608
}
536609

Sources/NIO/Channel.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public protocol ChannelCore: class {
3030
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
3131
func register0(promise: EventLoopPromise<Void>?)
3232

33+
/// Register channel as already connected or bound socket.
34+
/// - parameters:
35+
/// - promise: The `EventLoopPromise` which should be notified once the operation completes, or nil if no notification should take place.
36+
func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?)
37+
3338
/// Bind to a `SocketAddress`.
3439
///
3540
/// - parameters:
@@ -205,6 +210,10 @@ extension Channel {
205210
pipeline.register(promise: promise)
206211
}
207212

213+
public func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) {
214+
promise?.fail(error: ChannelError.operationUnsupported)
215+
}
216+
208217
public func triggerUserOutboundEvent(_ event: Any, promise: EventLoopPromise<Void>?) {
209218
pipeline.triggerUserOutboundEvent(event, promise: promise)
210219
}

Sources/NIO/DeadChannel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ private final class DeadChannelCore: ChannelCore {
2828
promise?.fail(error: ChannelError.ioOnClosedChannel)
2929
}
3030

31+
func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) {
32+
promise?.fail(error: ChannelError.ioOnClosedChannel)
33+
}
34+
3135
func bind0(to: SocketAddress, promise: EventLoopPromise<Void>?) {
3236
promise?.fail(error: ChannelError.ioOnClosedChannel)
3337
}

Sources/NIO/Embedded.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ class EmbeddedChannelCore: ChannelCore {
203203
pipeline.fireChannelRegistered0()
204204
}
205205

206+
func registerAlreadyConfigured0(promise: EventLoopPromise<Void>?) {
207+
isActive = true
208+
register0(promise: promise)
209+
pipeline.fireChannelActive0()
210+
}
211+
206212
func write0(_ data: NIOAny, promise: EventLoopPromise<Void>?) {
207213
guard let data = data.tryAsIOData() else {
208214
promise?.fail(error: ChannelError.writeDataUnsupported)

Sources/NIO/ServerSocket.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@
3232
super.init(descriptor: sock)
3333
}
3434

35+
/// Create a new instance.
36+
///
37+
/// - parameters:
38+
/// - descriptor: The _Unix file descriptor_ representing the bound socket.
39+
/// - setNonBlocking: Set non-blocking mode on the socket.
40+
/// - throws: An `IOError` if socket is invalid.
41+
init(descriptor: CInt, setNonBlocking: Bool = false) throws {
42+
super.init(descriptor: descriptor)
43+
if setNonBlocking {
44+
try self.setNonBlocking()
45+
}
46+
}
47+
3548
/// Start to listen for new connections.
3649
///
3750
/// - parameters:

Sources/NIO/Socket.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,27 @@ public typealias IOVector = iovec
3838
super.init(descriptor: sock)
3939
}
4040

41+
/// Create a new instance out of an already established socket.
42+
///
43+
/// - parameters:
44+
/// - descriptor: The existing socket descriptor.
45+
/// - setNonBlocking: Set non-blocking mode on the socket.
46+
/// - throws: An `IOError` if could not change the socket into non-blocking
47+
init(descriptor: CInt, setNonBlocking: Bool) throws {
48+
super.init(descriptor: descriptor)
49+
if setNonBlocking {
50+
try self.setNonBlocking()
51+
}
52+
}
53+
4154
/// Create a new instance.
4255
///
4356
/// The ownership of the passed in descriptor is transferred to this class. A user must call `close` to close the underlying
4457
/// file descriptor once its not needed / used anymore.
4558
///
4659
/// - parameters:
4760
/// - descriptor: The file descriptor to wrap.
48-
override init(descriptor: Int32) {
61+
override init(descriptor: CInt) {
4962
super.init(descriptor: descriptor)
5063
}
5164

0 commit comments

Comments
 (0)