Directly support the allowLocalEndpointReuse channel option (#82)

Motivation:

The NetworkFramework directly supports the concept of allowLocalEndPointReuse. Currently to get through the nio system, this is mapped to the SO_REUSEADDR option.

Now there is a shorthand option for this, it is sad to map from allowLocalEndPointReuse to SO_REUSEADDR and back again.

Modifications:

Add a new NIOTS channel option for this setting.
Set the underlying based on any of new setting, reuseAddr or reusePort.
Add a override to interpret the shorthand option directly into the new option.
Add shorthand options for client with tests of equivalence to long options.

Result:

Behaviour is identical but we can feel happier that the option mapping is less confusing.
This commit is contained in:
Peter Adams 2020-07-01 14:51:31 +01:00 committed by GitHub
parent f9a95e9bbb
commit cd49a10c4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 7 deletions

View File

@ -23,7 +23,7 @@ let package = Package(
.executable(name: "NIOTSHTTPServer", targets: ["NIOTSHTTPServer"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.15.0"),
.package(url: "https://github.com/apple/swift-nio.git", from: "2.19.0"),
],
targets: [
.target(name: "NIOTransportServices",

View File

@ -23,6 +23,9 @@ public struct NIOTSChannelOptions {
public static let enablePeerToPeer = NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption()
/// - See: NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse
public static let allowLocalEndpointReuse = NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse()
public static let currentPath = NIOTSChannelOptions.Types.NIOTSCurrentPathOption()
public static let metadata = { (definition: NWProtocolDefinition) -> NIOTSChannelOptions.Types.NIOTSMetadataOption in
@ -55,7 +58,6 @@ extension NIOTSChannelOptions {
public init() {}
}
/// `NIOTSEnablePeerToPeerOption` controls whether the `Channel` will advertise services using peer-to-peer
/// connectivity. Setting this to true is the equivalent of setting `NWParameters.enablePeerToPeer` to
/// `true`. By default this option is set to `false`.
@ -68,6 +70,18 @@ extension NIOTSChannelOptions {
public init() {}
}
/// `NIOTSAllowLocalEndpointReuse` controls whether the `Channel` can reuse a TCP address recently used.
/// Setting this to true is the equivalent of setting at least one of REUSEADDR and REUSEPORT to
/// `true`. By default this option is set to `false`.
///
/// This option must be set on the bootstrap: setting it after the channel is initialized will have no effect.
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
public struct NIOTSAllowLocalEndpointReuse: ChannelOption, Equatable {
public typealias Value = Bool
public init() {}
}
/// `NIOTSCurrentPathOption` accesses the `NWConnection.currentPath` of the underlying connection.
///
/// This option is only valid with `NIOTSConnectionBootstrap`.

View File

@ -242,5 +242,17 @@ public final class NIOTSConnectionBootstrap {
}
@available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
extension NIOTSConnectionBootstrap: NIOClientTCPBootstrapProtocol {}
extension NIOTSConnectionBootstrap: NIOClientTCPBootstrapProtocol {
/// Apply any understood shorthand options to the bootstrap, removing them from the set of options if they are consumed.
/// - parameters:
/// - options: The options to try applying - the options applied should be consumed from here.
/// - returns: The updated bootstrap with any understood options applied.
public func _applyChannelConvenienceOptions(_ options: inout ChannelOptions.TCPConvenienceOptions) -> Self {
var toReturn = self
if options.consumeAllowLocalEndpointReuse().isSet {
toReturn = self.channelOption(NIOTSChannelOptions.allowLocalEndpointReuse, value: true)
}
return toReturn
}
}
#endif

View File

@ -217,6 +217,9 @@ internal final class NIOTSConnectionChannel {
/// The value of SO_REUSEPORT.
private var reusePort = false
/// The value of the allowLocalEndpointReuse option.
private var allowLocalEndpointReuse = false
/// Whether to use peer-to-peer connectivity when connecting to Bonjour services.
private var enablePeerToPeer = false
@ -343,6 +346,8 @@ extension NIOTSConnectionChannel: Channel {
}
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
self.enablePeerToPeer = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
default:
fatalError("option \(type(of: option)).\(option) not supported")
}
@ -388,6 +393,8 @@ extension NIOTSConnectionChannel: Channel {
return self.options.waitForActivity as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
return self.enablePeerToPeer as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
return self.allowLocalEndpointReuse as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSCurrentPathOption:
guard let currentPath = self.nwConnection?.currentPath else {
throw NIOTSErrors.NoCurrentPath()
@ -498,8 +505,8 @@ extension NIOTSConnectionChannel: StateManagedChannel {
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
// either or it's been explicitly set.
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort || self.allowLocalEndpointReuse
parameters.includePeerToPeer = self.enablePeerToPeer

View File

@ -77,6 +77,9 @@ internal final class NIOTSListenerChannel {
/// The value of SO_REUSEPORT.
private var reusePort = false
/// The value of the allowLocalEndpointReuse option.
private var allowLocalEndpointReuse = false
/// Whether to enable peer-to-peer connectivity when using Bonjour services.
private var enablePeerToPeer = false
@ -195,6 +198,8 @@ extension NIOTSListenerChannel: Channel {
}
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
self.enablePeerToPeer = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
self.allowLocalEndpointReuse = value as! NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption.Value
default:
fatalError("option \(option) not supported")
}
@ -232,6 +237,8 @@ extension NIOTSListenerChannel: Channel {
}
case is NIOTSChannelOptions.Types.NIOTSEnablePeerToPeerOption:
return self.enablePeerToPeer as! Option.Value
case is NIOTSChannelOptions.Types.NIOTSAllowLocalEndpointReuse:
return self.allowLocalEndpointReuse as! Option.Value
default:
fatalError("option \(option) not supported")
}
@ -309,8 +316,8 @@ extension NIOTSListenerChannel: StateManagedChannel {
}
// Network.framework munges REUSEADDR and REUSEPORT together, so we turn this on if we need
// either.
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort
// either or it's been explicitly set.
parameters.allowLocalEndpointReuse = self.reuseAddress || self.reusePort || self.allowLocalEndpointReuse
parameters.includePeerToPeer = self.enablePeerToPeer

View File

@ -246,6 +246,66 @@ final class NIOTSBootstrapTests: XCTestCase {
XCTAssertNil(NIOTSListenerBootstrap(validatingGroup: wrongELG, childGroup: correctELG))
XCTAssertNil(NIOTSListenerBootstrap(validatingGroup: wrongEL, childGroup: correctEL))
}
func testEndpointReuseShortcutOption() throws {
let group = NIOTSEventLoopGroup()
let listenerChannel = try NIOTSListenerBootstrap(group: group)
.bind(host: "127.0.0.1", port: 0)
.wait()
let bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: group),
tls: NIOInsecureNoTLS())
.channelConvenienceOptions([.allowLocalEndpointReuse])
let client = try bootstrap.connect(to: listenerChannel.localAddress!).wait()
let optionValue = try client.getOption(NIOTSChannelOptions.allowLocalEndpointReuse).wait()
try client.close().wait()
XCTAssertEqual(optionValue, true)
}
func testShorthandOptionsAreEquivalent() throws {
func setAndGetOption<Option>(option: Option,
_ applyOptions : (NIOClientTCPBootstrap) -> NIOClientTCPBootstrap)
throws -> Option.Value where Option : ChannelOption {
let group = NIOTSEventLoopGroup()
let listenerChannel = try NIOTSListenerBootstrap(group: group)
.bind(host: "127.0.0.1", port: 0)
.wait()
let bootstrap = applyOptions(NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: group),
tls: NIOInsecureNoTLS()))
let client = try bootstrap.connect(to: listenerChannel.localAddress!).wait()
let optionRead = try client.getOption(option).wait()
try client.close().wait()
return optionRead
}
func checkOptionEquivalence<Option>(longOption: Option, setValue: Option.Value,
shortOption: ChannelOptions.TCPConvenienceOption) throws
where Option : ChannelOption, Option.Value : Equatable {
let longSetValue = try setAndGetOption(option: longOption) { bs in
bs.channelOption(longOption, value: setValue)
}
let shortSetValue = try setAndGetOption(option: longOption) { bs in
bs.channelConvenienceOptions([shortOption])
}
let unsetValue = try setAndGetOption(option: longOption) { $0 }
XCTAssertEqual(longSetValue, shortSetValue)
XCTAssertNotEqual(longSetValue, unsetValue)
}
try checkOptionEquivalence(longOption: NIOTSChannelOptions.allowLocalEndpointReuse,
setValue: true,
shortOption: .allowLocalEndpointReuse)
try checkOptionEquivalence(longOption: ChannelOptions.allowRemoteHalfClosure,
setValue: true,
shortOption: .allowRemoteHalfClosure)
try checkOptionEquivalence(longOption: ChannelOptions.autoRead,
setValue: false,
shortOption: .disableAutoRead)
}
}
extension Channel {