Skip to content

Commit 15dbe6d

Browse files
AperenceLukasa
andauthored
Add an option to enable Multipath TCP on clients (#766)
Multipath TCP (MPTCP) is a TCP extension allowing to enhance the reliability of the network by using multiple interfaces. This extension provides a seamless handover between interfaces in case of deterioration of the connection on the original one. In the context of iOS and Mac OS X, it could be really interesting to leverage the capabilities of MPTCP as they could benefit from their multiple interfaces (ethernet + Wi-fi for Mac OS X, Wi-fi + cellular for iOS). This contribution introduces patches to HTTPClient.Configuration and establishment of the Bootstraps. A supplementary field "enableMultipath" was added to the configuration, allowing to request the use of MPTCP. This flag is then used when creating the channels to configure the client. Note that in the future, it might also be potentially interesting to offer more precise configuration options for MPTCP on MacOS, as the Network framework allows also to select a type of service, instead of just offering the option to create MPTCP connections. Currently, when enabling MPTCP, only the Handover mode is used. --------- Co-authored-by: Cory Benfield <[email protected]>
1 parent 10bd49c commit 15dbe6d

File tree

3 files changed

+27
-0
lines changed

3 files changed

+27
-0
lines changed

Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift

+4
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ extension HTTPConnectionPool.ConnectionFactory {
322322
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {
323323
return tsBootstrap
324324
.channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity)
325+
.channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled)
325326
.connectTimeout(deadline - NIODeadline.now())
326327
.channelInitializer { channel in
327328
do {
@@ -338,6 +339,7 @@ extension HTTPConnectionPool.ConnectionFactory {
338339
if let nioBootstrap = ClientBootstrap(validatingGroup: eventLoop) {
339340
return nioBootstrap
340341
.connectTimeout(deadline - NIODeadline.now())
342+
.enableMPTCP(clientConfiguration.enableMultipath)
341343
}
342344

343345
preconditionFailure("No matching bootstrap found")
@@ -415,6 +417,7 @@ extension HTTPConnectionPool.ConnectionFactory {
415417

416418
tsBootstrap
417419
.channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity)
420+
.channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled)
418421
.connectTimeout(deadline - NIODeadline.now())
419422
.tlsOptions(options)
420423
.channelInitializer { channel in
@@ -443,6 +446,7 @@ extension HTTPConnectionPool.ConnectionFactory {
443446

444447
let bootstrap = ClientBootstrap(group: eventLoop)
445448
.connectTimeout(deadline - NIODeadline.now())
449+
.enableMPTCP(clientConfiguration.enableMultipath)
446450
.channelInitializer { channel in
447451
sslContextFuture.flatMap { sslContext -> EventLoopFuture<Void> in
448452
do {

Sources/AsyncHTTPClient/HTTPClient.swift

+5
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,10 @@ public class HTTPClient {
738738
}
739739
}
740740

741+
/// Whether ``HTTPClient`` will use Multipath TCP or not
742+
/// By default, don't use it
743+
public var enableMultipath: Bool
744+
741745
public init(
742746
tlsConfiguration: TLSConfiguration? = nil,
743747
redirectConfiguration: RedirectConfiguration? = nil,
@@ -755,6 +759,7 @@ public class HTTPClient {
755759
self.decompression = decompression
756760
self.httpVersion = .automatic
757761
self.networkFrameworkWaitForConnectivity = true
762+
self.enableMultipath = false
758763
}
759764

760765
public init(tlsConfiguration: TLSConfiguration? = nil,

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+18
Original file line numberDiff line numberDiff line change
@@ -3590,6 +3590,24 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
35903590
XCTAssertEqual(.ok, response.status)
35913591
}
35923592

3593+
func testClientWithMultipath() throws {
3594+
do {
3595+
var conf = HTTPClient.Configuration()
3596+
conf.enableMultipath = true
3597+
let client = HTTPClient(configuration: conf)
3598+
defer {
3599+
XCTAssertNoThrow(try client.shutdown().wait())
3600+
}
3601+
let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
3602+
XCTAssertEqual(.ok, response.status)
3603+
} catch let error as IOError where error.errnoCode == EINVAL || error.errnoCode == EPROTONOSUPPORT || error.errnoCode == ENOPROTOOPT {
3604+
// some old Linux kernels don't support MPTCP, skip this test in this case
3605+
// see https://www.mptcp.dev/implementation.html for details about each type
3606+
// of error
3607+
throw XCTSkip()
3608+
}
3609+
}
3610+
35933611
func testSingletonClientWorks() throws {
35943612
let response = try HTTPClient.shared.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
35953613
XCTAssertEqual(.ok, response.status)

0 commit comments

Comments
 (0)