Skip to content

Commit

Permalink
Add an option to enable Multipath TCP on clients
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Aperence committed Sep 16, 2024
1 parent e8babad commit b8faf9e
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ extension HTTPConnectionPool.ConnectionFactory {
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {
return tsBootstrap
.channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity)
.channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled)
.connectTimeout(deadline - NIODeadline.now())
.channelInitializer { channel in
do {
Expand All @@ -338,6 +339,7 @@ extension HTTPConnectionPool.ConnectionFactory {
if let nioBootstrap = ClientBootstrap(validatingGroup: eventLoop) {
return nioBootstrap
.connectTimeout(deadline - NIODeadline.now())
.enableMPTCP(clientConfiguration.enableMultipath)
}

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

tsBootstrap
.channelOption(NIOTSChannelOptions.waitForActivity, value: self.clientConfiguration.networkFrameworkWaitForConnectivity)
.channelOption(NIOTSChannelOptions.multipathServiceType, value: self.clientConfiguration.enableMultipath ? .handover : .disabled)
.connectTimeout(deadline - NIODeadline.now())
.tlsOptions(options)
.channelInitializer { channel in
Expand Down Expand Up @@ -443,6 +446,7 @@ extension HTTPConnectionPool.ConnectionFactory {

let bootstrap = ClientBootstrap(group: eventLoop)
.connectTimeout(deadline - NIODeadline.now())
.enableMPTCP(clientConfiguration.enableMultipath)
.channelInitializer { channel in
sslContextFuture.flatMap { sslContext -> EventLoopFuture<Void> in
do {
Expand Down
5 changes: 5 additions & 0 deletions Sources/AsyncHTTPClient/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,10 @@ public class HTTPClient {
}
}

/// Whether ``HTTPClient`` will use Multipath TCP or not
/// By default, don't use it
public var enableMultipath: Bool

public init(
tlsConfiguration: TLSConfiguration? = nil,
redirectConfiguration: RedirectConfiguration? = nil,
Expand All @@ -755,6 +759,7 @@ public class HTTPClient {
self.decompression = decompression
self.httpVersion = .automatic
self.networkFrameworkWaitForConnectivity = true
self.enableMultipath = false
}

public init(tlsConfiguration: TLSConfiguration? = nil,
Expand Down
15 changes: 15 additions & 0 deletions Tests/AsyncHTTPClientTests/HTTPClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3590,6 +3590,21 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
XCTAssertEqual(.ok, response.status)
}

func testClientWithMultipath() throws {
do{
var conf = HTTPClient.Configuration()
conf.enableMultipath = true
let client = HTTPClient(configuration: conf)
defer {
XCTAssertNoThrow(try client.shutdown().wait())
}
let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
XCTAssertEqual(.ok, response.status)
}catch{
// some old Linux kernels don't support MPTCP, skip this test in this case
}
}

func testSingletonClientWorks() throws {
let response = try HTTPClient.shared.get(url: self.defaultHTTPBinURLPrefix + "get").wait()
XCTAssertEqual(.ok, response.status)
Expand Down

0 comments on commit b8faf9e

Please sign in to comment.