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.
|
||||
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`.
|
||||
///
|
||||
/// Note that `NIOTSConnectionChannel` objects cannot be created on arbitrary loops types.
|
||||
|
|
@ -284,7 +290,16 @@ extension NIOTSConnectionChannel: Channel {
|
|||
self.options.supportRemoteHalfClosure = value as! Bool
|
||||
case _ 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:
|
||||
if self.backpressureManager.writabilityChanges(whenUpdatingWaterMarks: value as! WriteBufferWaterMark) {
|
||||
self.pipeline.fireChannelWritabilityChanged()
|
||||
|
|
@ -318,7 +333,16 @@ extension NIOTSConnectionChannel: Channel {
|
|||
return self.options.supportRemoteHalfClosure as! T.OptionType
|
||||
case _ 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:
|
||||
return self.backpressureManager.waterMarks as! T.OptionType
|
||||
default:
|
||||
|
|
@ -396,6 +420,11 @@ extension NIOTSConnectionChannel: StateManagedChannel {
|
|||
self.connectPromise = promise
|
||||
|
||||
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)
|
||||
connection.stateUpdateHandler = self.stateUpdateHandler(newState:)
|
||||
connection.betterPathUpdateHandler = self.betterPathHandler
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ internal final class NIOTSListenerChannel {
|
|||
/// Whether autoRead is enabled for this channel.
|
||||
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`.
|
||||
///
|
||||
/// Note that `NIOTSListenerChannel` objects cannot be created on arbitrary loops types.
|
||||
|
|
@ -148,7 +154,16 @@ extension NIOTSListenerChannel: Channel {
|
|||
}
|
||||
case _ 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:
|
||||
fatalError("option \(option) not supported")
|
||||
}
|
||||
|
|
@ -176,7 +191,16 @@ extension NIOTSListenerChannel: Channel {
|
|||
return autoRead as! T.OptionType
|
||||
case _ 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:
|
||||
fatalError("option \(option) not supported")
|
||||
}
|
||||
|
|
@ -247,6 +271,10 @@ extension NIOTSListenerChannel: StateManagedChannel {
|
|||
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
|
||||
do {
|
||||
listener = try NWListener(using: parameters)
|
||||
|
|
|
|||
|
|
@ -425,4 +425,42 @@ class NIOTSConnectionChannelTests: XCTestCase {
|
|||
XCTAssertTrue(connection.isWritable)
|
||||
}.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])
|
||||
}.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