Add support for SO_REUSEADDR and SO_REUSEPORT. (#1)
Motivation: These are commonly-set socket options for channels, and we should support them when using Transport Services as well. Modifications: Pass the socket options through to allowLocalEndpointReuse. Result: SO_REUSEADDR and SO_REUSEPORT will be available.
This commit is contained in:
parent
6475881aea
commit
955d1b91e7
|
|
@ -189,6 +189,12 @@ internal final class NIOTSConnectionChannel {
|
||||||
/// An object to keep track of pending writes and manage our backpressure signaling.
|
/// An object to keep track of pending writes and manage our backpressure signaling.
|
||||||
private var backpressureManager = BackpressureManager()
|
private var backpressureManager = BackpressureManager()
|
||||||
|
|
||||||
|
/// The value of SO_REUSEADDR.
|
||||||
|
private var reuseAddress = false
|
||||||
|
|
||||||
|
/// The value of SO_REUSEPORT.
|
||||||
|
private var reusePort = false
|
||||||
|
|
||||||
/// Create a `NIOTSConnectionChannel` on a given `NIOTSEventLoop`.
|
/// Create a `NIOTSConnectionChannel` on a given `NIOTSEventLoop`.
|
||||||
///
|
///
|
||||||
/// Note that `NIOTSConnectionChannel` objects cannot be created on arbitrary loops types.
|
/// Note that `NIOTSConnectionChannel` objects cannot be created on arbitrary loops types.
|
||||||
|
|
@ -284,7 +290,16 @@ extension NIOTSConnectionChannel: Channel {
|
||||||
self.options.supportRemoteHalfClosure = value as! Bool
|
self.options.supportRemoteHalfClosure = value as! Bool
|
||||||
case _ as SocketOption:
|
case _ as SocketOption:
|
||||||
let optionValue = option as! SocketOption
|
let optionValue = option as! SocketOption
|
||||||
try self.tcpOptions.applyChannelOption(option: optionValue, value: value as! SocketOptionValue)
|
|
||||||
|
// SO_REUSEADDR and SO_REUSEPORT are handled here.
|
||||||
|
switch optionValue.value {
|
||||||
|
case (SOL_SOCKET, SO_REUSEADDR):
|
||||||
|
self.reuseAddress = (value as! SocketOptionValue) != Int32(0)
|
||||||
|
case (SOL_SOCKET, SO_REUSEPORT):
|
||||||
|
self.reusePort = (value as! SocketOptionValue) != Int32(0)
|
||||||
|
default:
|
||||||
|
try self.tcpOptions.applyChannelOption(option: optionValue, value: value as! SocketOptionValue)
|
||||||
|
}
|
||||||
case _ as WriteBufferWaterMarkOption:
|
case _ as WriteBufferWaterMarkOption:
|
||||||
if self.backpressureManager.writabilityChanges(whenUpdatingWaterMarks: value as! WriteBufferWaterMark) {
|
if self.backpressureManager.writabilityChanges(whenUpdatingWaterMarks: value as! WriteBufferWaterMark) {
|
||||||
self.pipeline.fireChannelWritabilityChanged()
|
self.pipeline.fireChannelWritabilityChanged()
|
||||||
|
|
@ -318,7 +333,16 @@ extension NIOTSConnectionChannel: Channel {
|
||||||
return self.options.supportRemoteHalfClosure as! T.OptionType
|
return self.options.supportRemoteHalfClosure as! T.OptionType
|
||||||
case _ as SocketOption:
|
case _ as SocketOption:
|
||||||
let optionValue = option as! SocketOption
|
let optionValue = option as! SocketOption
|
||||||
return try self.tcpOptions.valueFor(socketOption: optionValue) as! T.OptionType
|
|
||||||
|
// SO_REUSEADDR and SO_REUSEPORT are handled here.
|
||||||
|
switch optionValue.value {
|
||||||
|
case (SOL_SOCKET, SO_REUSEADDR):
|
||||||
|
return Int32(self.reuseAddress ? 1 : 0) as! T.OptionType
|
||||||
|
case (SOL_SOCKET, SO_REUSEPORT):
|
||||||
|
return Int32(self.reusePort ? 1 : 0) as! T.OptionType
|
||||||
|
default:
|
||||||
|
return try self.tcpOptions.valueFor(socketOption: optionValue) as! T.OptionType
|
||||||
|
}
|
||||||
case _ as WriteBufferWaterMarkOption:
|
case _ as WriteBufferWaterMarkOption:
|
||||||
return self.backpressureManager.waterMarks as! T.OptionType
|
return self.backpressureManager.waterMarks as! T.OptionType
|
||||||
default:
|
default:
|
||||||
|
|
@ -396,6 +420,11 @@ extension NIOTSConnectionChannel: StateManagedChannel {
|
||||||
self.connectPromise = promise
|
self.connectPromise = promise
|
||||||
|
|
||||||
let parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions)
|
let parameters = NWParameters(tls: self.tlsOptions, tcp: self.tcpOptions)
|
||||||
|
|
||||||
|
// Network.framework munges REUSEADDR and REUSEPORT together, so we turn this on if we need
|
||||||
|
// either.
|
||||||
|
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort
|
||||||
|
|
||||||
let connection = NWConnection(to: target, using: parameters)
|
let connection = NWConnection(to: target, using: parameters)
|
||||||
connection.stateUpdateHandler = self.stateUpdateHandler(newState:)
|
connection.stateUpdateHandler = self.stateUpdateHandler(newState:)
|
||||||
connection.betterPathUpdateHandler = self.betterPathHandler
|
connection.betterPathUpdateHandler = self.betterPathHandler
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,12 @@ internal final class NIOTSListenerChannel {
|
||||||
/// Whether autoRead is enabled for this channel.
|
/// Whether autoRead is enabled for this channel.
|
||||||
private var autoRead: Bool = true
|
private var autoRead: Bool = true
|
||||||
|
|
||||||
|
/// The value of SO_REUSEADDR.
|
||||||
|
private var reuseAddress = false
|
||||||
|
|
||||||
|
/// The value of SO_REUSEPORT.
|
||||||
|
private var reusePort = false
|
||||||
|
|
||||||
/// Create a `NIOTSListenerChannel` on a given `NIOTSEventLoop`.
|
/// Create a `NIOTSListenerChannel` on a given `NIOTSEventLoop`.
|
||||||
///
|
///
|
||||||
/// Note that `NIOTSListenerChannel` objects cannot be created on arbitrary loops types.
|
/// Note that `NIOTSListenerChannel` objects cannot be created on arbitrary loops types.
|
||||||
|
|
@ -148,7 +154,16 @@ extension NIOTSListenerChannel: Channel {
|
||||||
}
|
}
|
||||||
case _ as SocketOption:
|
case _ as SocketOption:
|
||||||
let optionValue = option as! SocketOption
|
let optionValue = option as! SocketOption
|
||||||
try self.tcpOptions.applyChannelOption(option: optionValue, value: value as! SocketOptionValue)
|
|
||||||
|
// SO_REUSEADDR and SO_REUSEPORT are handled here.
|
||||||
|
switch optionValue.value {
|
||||||
|
case (SOL_SOCKET, SO_REUSEADDR):
|
||||||
|
self.reuseAddress = (value as! SocketOptionValue) != Int32(0)
|
||||||
|
case (SOL_SOCKET, SO_REUSEPORT):
|
||||||
|
self.reusePort = (value as! SocketOptionValue) != Int32(0)
|
||||||
|
default:
|
||||||
|
try self.tcpOptions.applyChannelOption(option: optionValue, value: value as! SocketOptionValue)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fatalError("option \(option) not supported")
|
fatalError("option \(option) not supported")
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +191,16 @@ extension NIOTSListenerChannel: Channel {
|
||||||
return autoRead as! T.OptionType
|
return autoRead as! T.OptionType
|
||||||
case _ as SocketOption:
|
case _ as SocketOption:
|
||||||
let optionValue = option as! SocketOption
|
let optionValue = option as! SocketOption
|
||||||
return try self.tcpOptions.valueFor(socketOption: optionValue) as! T.OptionType
|
|
||||||
|
// SO_REUSEADDR and SO_REUSEPORT are handled here.
|
||||||
|
switch optionValue.value {
|
||||||
|
case (SOL_SOCKET, SO_REUSEADDR):
|
||||||
|
return Int32(self.reuseAddress ? 1 : 0) as! T.OptionType
|
||||||
|
case (SOL_SOCKET, SO_REUSEPORT):
|
||||||
|
return Int32(self.reusePort ? 1 : 0) as! T.OptionType
|
||||||
|
default:
|
||||||
|
return try self.tcpOptions.valueFor(socketOption: optionValue) as! T.OptionType
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
fatalError("option \(option) not supported")
|
fatalError("option \(option) not supported")
|
||||||
}
|
}
|
||||||
|
|
@ -247,6 +271,10 @@ extension NIOTSListenerChannel: StateManagedChannel {
|
||||||
parameters.requiredInterface = interface
|
parameters.requiredInterface = interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Network.framework munges REUSEADDR and REUSEPORT together, so we turn this on if we need
|
||||||
|
// either.
|
||||||
|
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort
|
||||||
|
|
||||||
let listener: NWListener
|
let listener: NWListener
|
||||||
do {
|
do {
|
||||||
listener = try NWListener(using: parameters)
|
listener = try NWListener(using: parameters)
|
||||||
|
|
|
||||||
|
|
@ -425,4 +425,42 @@ class NIOTSConnectionChannelTests: XCTestCase {
|
||||||
XCTAssertTrue(connection.isWritable)
|
XCTAssertTrue(connection.isWritable)
|
||||||
}.wait()
|
}.wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSettingGettingReuseaddr() throws {
|
||||||
|
let listener = try NIOTSListenerBootstrap(group: self.group).bind(host: "localhost", port: 0).wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try listener.close().wait())
|
||||||
|
}
|
||||||
|
let connection = try NIOTSConnectionBootstrap(group: self.group)
|
||||||
|
.connect(to: listener.localAddress!)
|
||||||
|
.wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try connection.close().wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(0, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
XCTAssertNoThrow(try connection.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 5).wait())
|
||||||
|
XCTAssertEqual(1, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
XCTAssertNoThrow(try connection.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 0).wait())
|
||||||
|
XCTAssertEqual(0, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSettingGettingReuseport() throws {
|
||||||
|
let listener = try NIOTSListenerBootstrap(group: self.group).bind(host: "localhost", port: 0).wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try listener.close().wait())
|
||||||
|
}
|
||||||
|
let connection = try NIOTSConnectionBootstrap(group: self.group)
|
||||||
|
.connect(to: listener.localAddress!)
|
||||||
|
.wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try connection.close().wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(0, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
XCTAssertNoThrow(try connection.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT), value: 5).wait())
|
||||||
|
XCTAssertEqual(1, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
XCTAssertNoThrow(try connection.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT), value: 0).wait())
|
||||||
|
XCTAssertEqual(0, try connection.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,4 +112,30 @@ class NIOTSListenerChannelTests: XCTestCase {
|
||||||
XCTAssertEqual(bindRecordingHandler.endpointTargets, [endpoint])
|
XCTAssertEqual(bindRecordingHandler.endpointTargets, [endpoint])
|
||||||
}.wait()
|
}.wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testSettingGettingReuseaddr() throws {
|
||||||
|
let listener = try NIOTSListenerBootstrap(group: self.group).bind(host: "localhost", port: 0).wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try listener.close().wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(0, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
XCTAssertNoThrow(try listener.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 5).wait())
|
||||||
|
XCTAssertEqual(1, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
XCTAssertNoThrow(try listener.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 0).wait())
|
||||||
|
XCTAssertEqual(0, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR)).wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSettingGettingReuseport() throws {
|
||||||
|
let listener = try NIOTSListenerBootstrap(group: self.group).bind(host: "localhost", port: 0).wait()
|
||||||
|
defer {
|
||||||
|
XCTAssertNoThrow(try listener.close().wait())
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(0, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
XCTAssertNoThrow(try listener.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT), value: 5).wait())
|
||||||
|
XCTAssertEqual(1, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
XCTAssertNoThrow(try listener.setOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT), value: 0).wait())
|
||||||
|
XCTAssertEqual(0, try listener.getOption(option: ChannelOptions.socket(SOL_SOCKET, SO_REUSEPORT)).wait())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue