Skip to content

Commit 1a7f321

Browse files
committed
Add an option to enable Multipath TCP on clients
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.
1 parent e8babad commit 1a7f321

File tree

2 files changed

+27
-9
lines changed

2 files changed

+27
-9
lines changed

Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift

Lines changed: 4 additions & 0 deletions
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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -738,14 +738,19 @@ 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,
744748
timeout: Timeout = Timeout(),
745749
connectionPool: ConnectionPool = ConnectionPool(),
746750
proxy: Proxy? = nil,
747751
ignoreUncleanSSLShutdown: Bool = false,
748-
decompression: Decompression = .disabled
752+
decompression: Decompression = .disabled,
753+
enableMultipath: Bool = false
749754
) {
750755
self.tlsConfiguration = tlsConfiguration
751756
self.redirectConfiguration = redirectConfiguration ?? RedirectConfiguration()
@@ -755,22 +760,25 @@ public class HTTPClient {
755760
self.decompression = decompression
756761
self.httpVersion = .automatic
757762
self.networkFrameworkWaitForConnectivity = true
763+
self.enableMultipath = enableMultipath
758764
}
759765

760766
public init(tlsConfiguration: TLSConfiguration? = nil,
761767
redirectConfiguration: RedirectConfiguration? = nil,
762768
timeout: Timeout = Timeout(),
763769
proxy: Proxy? = nil,
764770
ignoreUncleanSSLShutdown: Bool = false,
765-
decompression: Decompression = .disabled) {
771+
decompression: Decompression = .disabled,
772+
enableMultipath: Bool = false) {
766773
self.init(
767774
tlsConfiguration: tlsConfiguration,
768775
redirectConfiguration: redirectConfiguration,
769776
timeout: timeout,
770777
connectionPool: ConnectionPool(),
771778
proxy: proxy,
772779
ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
773-
decompression: decompression
780+
decompression: decompression,
781+
enableMultipath: enableMultipath
774782
)
775783
}
776784

@@ -780,7 +788,8 @@ public class HTTPClient {
780788
maximumAllowedIdleTimeInConnectionPool: TimeAmount = .seconds(60),
781789
proxy: Proxy? = nil,
782790
ignoreUncleanSSLShutdown: Bool = false,
783-
decompression: Decompression = .disabled) {
791+
decompression: Decompression = .disabled,
792+
enableMultipath: Bool = false) {
784793
var tlsConfig = TLSConfiguration.makeClientConfiguration()
785794
tlsConfig.certificateVerification = certificateVerification
786795
self.init(tlsConfiguration: tlsConfig,
@@ -789,7 +798,8 @@ public class HTTPClient {
789798
connectionPool: ConnectionPool(idleTimeout: maximumAllowedIdleTimeInConnectionPool),
790799
proxy: proxy,
791800
ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
792-
decompression: decompression)
801+
decompression: decompression,
802+
enableMultipath: enableMultipath)
793803
}
794804

795805
public init(certificateVerification: CertificateVerification,
@@ -799,7 +809,8 @@ public class HTTPClient {
799809
proxy: Proxy? = nil,
800810
ignoreUncleanSSLShutdown: Bool = false,
801811
decompression: Decompression = .disabled,
802-
backgroundActivityLogger: Logger?) {
812+
backgroundActivityLogger: Logger?,
813+
enableMultipath: Bool = false) {
803814
var tlsConfig = TLSConfiguration.makeClientConfiguration()
804815
tlsConfig.certificateVerification = certificateVerification
805816
self.init(tlsConfiguration: tlsConfig,
@@ -808,23 +819,26 @@ public class HTTPClient {
808819
connectionPool: ConnectionPool(idleTimeout: connectionPool),
809820
proxy: proxy,
810821
ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
811-
decompression: decompression)
822+
decompression: decompression,
823+
enableMultipath: enableMultipath)
812824
}
813825

814826
public init(certificateVerification: CertificateVerification,
815827
redirectConfiguration: RedirectConfiguration? = nil,
816828
timeout: Timeout = Timeout(),
817829
proxy: Proxy? = nil,
818830
ignoreUncleanSSLShutdown: Bool = false,
819-
decompression: Decompression = .disabled) {
831+
decompression: Decompression = .disabled,
832+
enableMultipath: Bool = false) {
820833
self.init(
821834
certificateVerification: certificateVerification,
822835
redirectConfiguration: redirectConfiguration,
823836
timeout: timeout,
824837
maximumAllowedIdleTimeInConnectionPool: .seconds(60),
825838
proxy: proxy,
826839
ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
827-
decompression: decompression
840+
decompression: decompression,
841+
enableMultipath: enableMultipath
828842
)
829843
}
830844
}

0 commit comments

Comments
 (0)