Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] add MPTCP support #132

Closed
wants to merge 1 commit into from
Closed

[core] add MPTCP support #132

wants to merge 1 commit into from

Conversation

mux99
Copy link
Contributor

@mux99 mux99 commented Mar 14, 2024

Multipath TCP (MPTCP), standardized in RFC8684 [1], is a TCP extension
that enables a TCP connection to use different paths.
Multipath TCP has been used for several use cases.
On smartphones, Multipath TCP enables seamless handovers between
cellular and Wi-Fi networks while preserving established connections.
This use-case is what pushed Apple to use MPTCP since 2013 in multiple
applications [2]. On dual-stack hosts, Multipath TCP enables the TCP
connection to automatically use the best performing path,
either IPv4 or IPv6. If one path fails, Multipath TCP automatically
uses the other path. To benefit from MPTCP, both the client and the
server have to support it.

Multipath TCP is a backward-compatible TCP extension that is enabled by
default on recent Linux distributions (Debian, Ubuntu, Redhat, ...).
Multipath TCP is included in the Linux kernel since version 5.6 [3].
To use Multipath TCP on Linux, a server application must explicitly
enable it when creating the socket. No need to change anything else in
the application. This is what the attached patch is doing.

It is important to note that a MPTCP-enabled server continues to accept
regular TCP connections that do not use the Multipath TCP extension
without any performance impact.

When a connection request is received, and is linked to a listening
socket with MPTCP support, the kernel will simply check if MPTCP
options are present. If not, the accepted socket will be a "plain" TCP
one.

This commit modifies the creation of sockets on Linux, trying to use
Multipath TCP is available. If not, a plain TCP socket is created
instead. A new option is now available: server.network-mptcp. This
option is enabled by default. It seems important to enable this option
by default on the server side, to let clients using MPTCP while not
degrading performances when "plain" TCP is used. If the option is not
enabled by default, it looks difficult to convince each administrator to
enable it explicitly. This technology is mainly useful for the client
side while it doesn't change much for the server side where no other
configuration is needed.

Link: https://www.rfc-editor.org/rfc/rfc8684.html [1]
Link: https://www.tessares.net/apples-mptcp-story-so-far/ [2]
Link: https://www.mptcp.dev [3]
Co-developed-by: Maxime Dourov mux99@live.be
Co-developed-by: Olivier Bonaventure Olivier.Bonaventure@uclouvain.be
Co-developed-by: Matthieu Baerts matttbe@kernel.org
Signed-off-by: Maxime Dourov (UCLouvain) mux99@live.be

@gstrauss
Copy link
Member

gstrauss commented Mar 14, 2024

Thank you for suggesting the patch.

If the feature was disabled by default, I would accept the patch as submitted. However, you are suggesting that this feature be enabled by default. Your entire justification is "If the option is not enabled by default, it looks difficult to convince each administrator to enable it explicitly" which translates to little more than "I want clients to be able to use this feature, so enable it by default on servers".

First, what are the security and privacy implications, if any, to client or server, of enabling this by default in lighttpd?
What are the potential implications, if any, for enabling this on servers reached via less-than-intelligent proxies?

Second, would you please point me to established documentation to corroborate your statement that enabling MPTCP on listening sockets is "without any performance impact"? https://www.mptcp.dev/ notes the feature "Fallback from MPTCP to TCP if the peer or a middlebox do not support MPTCP." There is no performance impact to this? Fallback is good. However, lighttpd may be run on very small embedded systems, so extra cost needs to be accurately accounted, even if there is transparent fallback so that things do not break.

Third, if enabling this by default, perhaps this feature should be available only when IPPROTO_MPTCP is defined, and the code block enabling the feature should be wrapped in #ifdef IPPROTO_MPTCP ... #endif, rather than defining the missing define. Otherwise, log_pdebug() will end up issuing trace for everyone running newer versions of lighttpd on old systems, since log_pdebug() ends up in the error log, which is the default used by many people. log_pdebug() is filtered by syslog only if people have configured lighttpd logging to use syslog, which is not the default in lighttpd.

Fourth, is multipath TCP available only on Linux? I think it is more widely available. Restricting it to Linux is a limitation of this patch. However, multipath TCP must also be available on macOS and iOS if Apple enables it in clients. lighttpd can be run on a wide variety of servers, including some which you might consider clients such as phones (e.g. Android or iOS).


To be clear, I would like to support MPTCP and this patch, but I have questions since I am not an expert in MPTCP. Before enabling this by default in lighttpd, I need to have a stronger understanding of potential implications and effects of doing so. Thank you.

@gstrauss
Copy link
Member

gstrauss commented Mar 14, 2024

FreeBSD support for multipath TCP: https://freebsdfoundation.org/project/multipath-tcp-for-freebsd/

Edit: someone tried to compile a status of MPTCP in FreeBSD in 2023
https://www-cs-students.stanford.edu/~sjac/freebsd_mptcp_info.html

Conclusions:

MPTCP is not supported by FreeBSD. Earlier work on MPTCP from the Swinburne CAIA project is no longer supported, and can no longer be patched into recent FreeBSD source code without extensive work.

@gstrauss
Copy link
Member

gstrauss commented Mar 14, 2024

Regarding the git usage of this PR, please do not merge the master branch. Instead, git rebase master (or git rebase upstream/master if the official lighttpd repository is a git remote named 'upstream' in your local git repo). You can git checkout -b mptcp upstream/master and then git cherry-pick ab136194 to have a cleaner mptcp feature branch without the merge. In general, it is good practice to work on feature branches (instead of 'master') when submitting PRs or merge requests to other repositories.

@gstrauss
Copy link
Member

How does MPTCP (at the transport layer) compare with the feature of HTTP/3 QUIC streams (closer to the application layers) which can be transported over multiple paths?

Multipath TCP (MPTCP), standardized in RFC8684 [1], is a TCP extension
that enables a TCP connection to use different paths.
Multipath TCP has been used for several use cases.
On smartphones, Multipath TCP enables seamless handovers between
cellular and Wi-Fi networks while preserving established connections.
This use-case is what pushed Apple to use MPTCP since 2013 in multiple
applications [2]. On dual-stack hosts, Multipath TCP enables the TCP
connection to automatically use the best performing path,
either IPv4 or IPv6. If one path fails, Multipath TCP automatically
uses the other path. To benefit from MPTCP, both the client and the
server have to support it.

Multipath TCP is a backward-compatible TCP extension that is enabled by
default on recent Linux distributions (Debian, Ubuntu, Redhat, ...).
Multipath TCP is included in the Linux kernel since version 5.6 [3].
To use Multipath TCP on Linux, a server application must explicitly
enable it when creating the socket. No need to change anything else in
the application. This is what the attached patch is doing.

It is important to note that a MPTCP-enabled server continues to accept
regular TCP connections that do not use the Multipath TCP extension
without any performance impact.

When a connection request is received, and is linked to a listening
socket with MPTCP support, the kernel will simply check if MPTCP
options are present. If not, the accepted socket will be a "plain" TCP
one.

This commit modifies the creation of sockets on Linux, trying to use
Multipath TCP is available. If not, a plain TCP socket is created
instead. A new option is now available: `server.network-mptcp`. This
option is enabled by default. It seems important to enable this option
by default on the server side, to let clients using MPTCP while not
degrading performances when "plain" TCP is used. If the option is not
enabled by default, it looks difficult to convince each administrator to
enable it explicitly. This technology is mainly useful for the client
side while it doesn't change much for the server side where no other
configuration is needed.

Link: https://www.rfc-editor.org/rfc/rfc8684.html [1]
Link: https://www.tessares.net/apples-mptcp-story-so-far/ [2]
Link: https://www.mptcp.dev [3]
Co-developed-by: Maxime Dourov <mux99@live.be>
Co-developed-by: Olivier Bonaventure <Olivier.Bonaventure@uclouvain.be>
Co-developed-by: Matthieu Baerts <matttbe@kernel.org>
Signed-off-by: Maxime Dourov (UCLouvain) <mux99@live.be>
@matttbe
Copy link

matttbe commented Mar 15, 2024

Hi @gstrauss

(I worked on this patch with Maxime)

Thank you for suggesting the patch.

Thank you for your review and the question.

If the feature was disabled by default, I would accept the patch as submitted. However, you are suggesting that this feature be enabled by default. Your entire justification is "If the option is not enabled by default, it looks difficult to convince each administrator to enable it explicitly" which translates to little more than "I want clients to be able to use this feature, so enable it by default on servers".

We understand your reaction. I think adding a bit more context about our work here with Maxime at UCLouvain might help.

UCLouvain is a university in Belgium where a lot of development around Multipath TCP (and Multipath QUIC) has been done. Today, MPTCP is available in the Linux kernel and iOS, but is mainly used by big companies: mainly Apple for some of their apps, some specific use-cases in datacenters, and ISPs to use both the cellular and the fixed network for some clients. In all these cases, the companies behind them control both the clients and the servers. If someone wants to have an app using MPTCP, they often simply use a tool like mptcpize to force the creation of an MPTCP socket, instead of a TCP one, thanks to LD_PRELOAD (or eBPF, or systemtap) as explained in different articles, like here. That's great and easy, but it doesn't help to spread its usage: people will only use it with services they control, and some sysadmins might not like using LD_PRELOAD. It is frustrating to have to tell people to deploy a proxy to benefit from MPTCP, e.g., by using multiple (slow) Internet links to have something decent. So I'm leading a new small project at the UCLouvain with Olivier Bonaventure, to improve the situation. (Note that I'm not affiliated with UCLouvain; I'm just helping here with this project; I'm working on my own on MPTCP in the kernel at the moment.)

With the help of two students who want to learn by doing useful things in open source, we are trying to add native support to different apps. Our "dream" would be to have MPTCP enabled by default on the server side, so people can start using it with multiple servers without a proxy or asking sysadmins to enable it. In our minds, because of its low impact, it should not matter to have it enabled for a sysadmin: it is in theory as secured as TCP (see below), the server will continue to serve files, and this might be done quicker, or without interruptions when the client is switching from one network to another.

But you are right; it is not as simple.

First, what are the security and privacy implications, if any, to client or server, of enabling this by default in lighttpd?

Regarding the security implications of the protocol, it is described in RFC 8684. In summary, the fundamental goal is for the security of MPTCP to be "no worse" than regular TCP today.

Regarding the security implications of the Linux kernel implementations, it is like any software; there might be issues. But the Linux kernel is also known to quickly fix security issues once they have been identified.

For the privacy concerns, the RFC doesn't mention anything, I think, but it looks like it is similar to TCP. Of course, here, if a client is doing a long transfer while switching from one network to another, the server can see it is the same client. Without MPTCP, the server would see the same requests twice, but from two different IPs. MPTCP can be used to improve privacy: if a path is known as being untrusted, but cheap, some data --e.g., TLS handshakes -- could be done over another path, trusted but more expensive.

What are the potential implications, if any, for enabling this on servers reached via less-than-intelligent proxies?

Sorry, I'm not sure if I understand the question. If these proxies initiating connections to our server don't support MPTCP, MPTCP will not be used. Technically, if a request is established without MPTCP options in the TCP header, the Linux kernel will create a "plain" TCP socket, which is used by the userspace after an accept().

Second, would you please point me to established documentation to corroborate your statement that enabling MPTCP on listening sockets is "without any performance impact"?

We need to work on such a document, sorry. Just to be precise, there is no real performance impact if the client doesn't support MPTCP; apart from checking if MPTCP options are present in the TCP header, that's negligible. Here is what the kernel code is saying when the userspace uses accept() when there are no (or bad) MPTCP options in the request:

/* we are being invoked after accepting a non-mp-capable
 * flow: sk is a tcp_sk, not an mptcp one.
 *
 * Hand the socket over to tcp so all further socket ops
 * bypass mptcp.
 */

When MPTCP is used, there is a small impact because there is an extra layer. It is difficult to measure the impact: we can do that when only one path is used, and see that the impact is extremely low, but it is mainly caused by a few bytes (~20B) added in the TCP header for MPTCP to work. When multiple subflows are used, the server will send data at a higher rate, or not have to re-send files.

https://www.mptcp.dev/ notes the feature "Fallback from MPTCP to TCP if the peer or a middlebox do not support MPTCP." There is no performance impact to this?

To be able to survive on the "wild" Internet, MPTCP connections should be able to fall back to TCP in case of issues. Typically, a client initiates an MPTCP connection, and a middlebox will drop MPTCP options from the TCP headers because they are "unknown". On the server side, it will receive a request without MPTCP options; that's fine: a plain TCP socket will be created. If MPTCP options are dropped (or altered) later, the MPTCP socket will continue to be used, but it will directly call the TCP layer. That's the "extra layer" we cannot avoid. But as it is done nicely on the kernel side, the impact is very low.

Fallback is good. However, lighttpd may be run on very small embedded systems, so extra cost needs to be accurately accounted, even if there is transparent fallback so that things do not break.

Good point. I don't have any numbers to share for embedded systems, even if I used to work for a company where we were installing a TCP-2-MPTCP proxy on residential gateways. These routers were less powerful than most Raspberry Pi's :)

Third, if enabling this by default, perhaps this feature should be available only when IPPROTO_MPTCP is defined, and the code block enabling the feature should be wrapped in #ifdef IPPROTO_MPTCP ... #endif, rather than defining the missing define. Otherwise, log_pdebug() will end up issuing trace for everyone running newer versions of lighttpd on old systems, since log_pdebug() ends up in the error log, which is the default used by many people. log_pdebug() is filtered by syslog only if people have configured lighttpd logging to use syslog, which is not the default in lighttpd.

I don't think it is a good idea to restrict this with #ifdef IPPROTO_MPTCP: IPPROTO_MPTCP is defined in the libC, independently of the kernel being used, potentially out-to-date compared to the kernel then. And this is at build time. Same to check at build time if MPTCP is available: that's potentially a different environment than the one used at run time. For example, Debian builds packages for any Debian versions on (old) stable versions servers, where they do a chroot with packages from different Debian versions. The kernel might not be the one used at runtime. (Or MPTCP could be disabled on these kernels used during the build.)

I didn't know about the impact of the log. We thought it would be OK because there will be only one log entry at the start of a server, no?
If this is still too many, should we maybe restrict it only if there was an unexpected errno? Creating a socket with IPPROTO_MPTCP will fail with either ENOPROTOOPT (Protocol not available: linked to net.mptcp.enabled sysctl), EPROTONOSUPPORT (Protocol not supported: MPTCP is not compiled on >= v5.6), or EINVAL (Invalid argument: MPTCP is not available on kernels < 5.6).

Fourth, is multipath TCP available only on Linux? I think it is more widely available. Restricting it to Linux is a limitation of this patch. However, multipath TCP must also be available on macOS and iOS if Apple enables it in clients. lighttpd can be run on a wide variety of servers, including some which you might consider clients such as phones (e.g. Android or iOS).

It is easily accessible with Linux, thanks to IPPROTO_MPTCP.

On iOS, it is available, but well:

  • It is easy if you use their SDK: doc
  • If not, you need to use private libraries (I'm not even sure the headers are available) with specific functions to create sockets, e.g., OpenSSH for iOS

On FreeBSD, there was an ongoing implementation, but that was years ago, as you mentioned.

There are other implementations, but on specific systems (Citrix load balancer, userspace, etc.):

So I think it is best to restrict this to Linux only for the moment.

Regarding the git usage of this PR, please do not merge the master branch.

Sorry for that: it was not supposed to be done. Maxime will make sure not to do that again.

How does MPTCP (at the transport layer) compare with the feature of HTTP/3 QUIC streams (closer to the application layers) which can be transported over multiple paths?

QUIC doesn't support using multiple paths at the same time. There is a draft, some implementations support it, but the standardization is slow. The idea there is apparently to let apps manage the different paths themselves. More freedom, but more complex than just changing the socket protocol. QUIC does support connections migrations, but it is not as smooth as a seamless handover done by MPTCP, as only one path can be used at a time with QUIC for the moment.

To be clear, I would like to support MPTCP and this patch, but I have questions since I am not an expert in MPTCP. Before enabling this by default in lighttpd, I need to have a stronger understanding of potential implications and effects of doing so. Thank you.

We understand.
Having an option to support MPTCP is already good; having the possibility to use MPTCP directly with more servers would be even better, if the cost is small enough :)

@gstrauss
Copy link
Member

gstrauss commented Mar 16, 2024

Thank you for the detailed response. I will be following the links and reading more about MPTCP in the coming days, so at this moment, I won't respond directly to your detailed comments.

In the meantime, I'd like to propose a practical approach to incrementally release these changes, even though the patch is small. I propose that this patch be committed with the feature disabled by default config_feature_bool(srv, "server.network-mptcp", 0), and in the next lighttpd release, the release notes can announce a future scheduled behavior change where this feature will be enabled by default. For example, there are a couple changes scheduled for the beginning of 2025 which were announced as part of the lighttpd 1.4.75 release: https://wiki.lighttpd.net/Release-1_4_75 . By releasing this new feature disabled by default, it will allow others to more easily try it and report issues before the feature becomes enabled by default.

The next lighttpd release is likely some time in Q3 2024 time-frame unless bugs pop up or major feature development completes sooner, so announcing the change for the beginning of 2025 seems like a reasonable target to me. I realize that may be disappointing, but lighttpd 1.4.75 was just recently released and I am attempting to get that into the next Ubuntu long-term release which is already in feature freeze.

In the details: On socket() with IPPROTO_MPTCP failure, log_pdebug() would be called once per listening socket at startup, e.g. for 0.0.0.0:80 and [::]:80 and 0.0.0.0:443 and [::]:443 and ... While this occurs only at startup, it is noise and could be alarming to someone unfamiliar were the new feature suddently enabled by default. Someone would have to change the lighttpd config to silence the trace. Now then, it is generally good practice to check the return values from system calls. In this case, if socket() with IPPROTO_MPTCP fails, MPTCP is probably disabled or unsupported, and the code will immediately try again with IPPROTO_TCP. If that fails, an error is reported. Therefore, I think the trace for IPPROTO_MPTCP can simply be removed, since it will probably not report any error of interest that failure with socket() with IPPROTO_TCP would not subsequently report. If the feature is disabled by default, maybe the trace should remain for now, and later be removed when the feature is enabled by default? I think that removing the trace is simplest.

  #ifdef __linux__
	if (config_feature_bool(srv, "server.network-mptcp", 0)) {
		/* manually define mptcp protocol number in case the compiler is using an older version of libc */
		#ifndef IPPROTO_MPTCP
		#define IPPROTO_MPTCP 262
		#endif
		srv_socket->fd = fdevent_socket_nb_cloexec(family, SOCK_STREAM, IPPROTO_MPTCP);
	}
	if (-1 != srv_socket->fd) { } else /*fallback to tcp*/
  #endif

Future looking: lighttpd mod_openssl and lighttpd mod_gnutls call uname() and check the linux kernel version for KTLS support. Runtime feature checks could be performed at startup to test for IPPROTO_MPTCP support, which I would suggest if keeping the log_pdebug() trace is desired. (I currently do not think the trace is needed, but can be convinced.)

@matttbe
Copy link

matttbe commented Mar 18, 2024

@gstrauss thank you for your reply!

Thank you for the detailed response. I will be following the links and reading more about MPTCP in the coming days, so at this moment, I won't respond directly to your detailed comments.

All good, take your time!

In the meantime, I'd like to propose a practical approach to incrementally release these changes, even though the patch is small. I propose that this patch be committed with the feature disabled by default config_feature_bool(srv, "server.network-mptcp", 0), and in the next lighttpd release, the release notes can announce a future scheduled behavior change where this feature will be enabled by default. For example, there are a couple changes scheduled for the beginning of 2025 which were announced as part of the lighttpd 1.4.75 release: https://wiki.lighttpd.net/Release-1_4_75 . By releasing this new feature disabled by default, it will allow others to more easily try it and report issues before the feature becomes enabled by default.

It sounds like a good idea!

Should Maxime update some doc, and/or the default config file to reflect that?

The next lighttpd release is likely some time in Q3 2024 time-frame unless bugs pop up or major feature development completes sooner, so announcing the change for the beginning of 2025 seems like a reasonable target to me. I realize that may be disappointing, but lighttpd 1.4.75 was just recently released and I am attempting to get that into the next Ubuntu long-term release which is already in feature freeze.

That's OK, best not to rush anyway.

In the details: On socket() with IPPROTO_MPTCP failure, log_pdebug() would be called once per listening socket at startup, e.g. for 0.0.0.0:80 and [::]:80 and 0.0.0.0:443 and [::]:443 and ... While this occurs only at startup, it is noise and could be alarming to someone unfamiliar were the new feature suddently enabled by default. Someone would have to change the lighttpd config to silence the trace. Now then, it is generally good practice to check the return values from system calls. In this case, if socket() with IPPROTO_MPTCP fails, MPTCP is probably disabled or unsupported, and the code will immediately try again with IPPROTO_TCP. If that fails, an error is reported. Therefore, I think the trace for IPPROTO_MPTCP can simply be removed, since it will probably not report any error of interest that failure with socket() with IPPROTO_TCP would not subsequently report. If the feature is disabled by default, maybe the trace should remain for now, and later be removed when the feature is enabled by default? I think that removing the trace is simplest.

Good point, if MPTCP is used by default, it would probably be best not to warn the user multiple times. But if MPTCP has been explicitly enabled, should we not keep the log entry to let users understanding there was an issue? Or only warn them in case errno is ENOPROTOOPT, EPROTONOSUPPORT or EINVAL (linked to my previous comment: specific errors with MPTCP)?

Future looking: lighttpd mod_openssl and lighttpd mod_gnutls call uname() and check the linux kernel version for KTLS support. Runtime feature checks could be performed at startup to test for IPPROTO_MPTCP support, which I would suggest if keeping the log_pdebug() trace is desired. (I currently do not think the trace is needed, but can be convinced.)

Interesting, that could be used indeed, but please note that even if the kernel is recent enough (v5.6), MPTCP might still not be available: not compiled (CONFIG_MPTCP), disabled via a sysctl or SELinux, etc.

Please also note that MPTCP is currently not compatible with KTLS: they both use the same technique to extend TCP behaviour in the kernel (TCP ULP). Technically, it should be possible to chain these extensions, but that's not possible today (mainly because nobody requested it I think). I didn't know KTLS was used by default in lighttpd.

@gstrauss
Copy link
Member

Good point, if MPTCP is used by default, it would probably be best not to warn the user multiple times. But if MPTCP has been explicitly enabled, should we not keep the log entry to let users understanding there was an issue? Or only warn them in case errno is ENOPROTOOPT, EPROTONOSUPPORT or EINVAL (linked to my previous comment: specific errors with MPTCP)?

In a world centered around MPTCP, yes that makes sense for you. On the other hand, I do not think adding many lines of code around this is worthwhile as this is linux-specific and a simple strace() can be used to see if socket() with IPPROTO_MPTCP succeeds, or with what errno it fails. MPTCP is a feature, and is not a critical failure if unavailable.

Future looking: lighttpd mod_openssl and lighttpd mod_gnutls call uname() and check the linux kernel version for KTLS support. Runtime feature checks could be performed at startup to test for IPPROTO_MPTCP support, which I would suggest if keeping the log_pdebug() trace is desired. (I currently do not think the trace is needed, but can be convinced.)

Interesting, that could be used indeed, but please note that even if the kernel is recent enough (v5.6), MPTCP might still not be available: not compiled (CONFIG_MPTCP), disabled via a sysctl or SELinux, etc.

lighttpd could at startup perform a single socket() with IPPROTO_MPTCP test and set a file-scoped static variable with whether or not IPPROTO_MPTCP is available, rather than checking uname() and sysctl and ...

Please also note that MPTCP is currently not compatible with KTLS: they both use the same technique to extend TCP behaviour in the kernel (TCP ULP). Technically, it should be possible to chain these extensions, but that's not possible today (mainly because nobody requested it I think). I didn't know KTLS was used by default in lighttpd.

Whether or not KTLS is enabled by default in lighttpd, that MPTCP and KTLS are not supported together should be carefully documented on the MPTCP site and this info shared with other projects to which your team sends patches.

What behavior/errors would someone see if they enabled MPTCP in lighttpd and also use lighttpd mod_openssl or lighttpd mod_gnutls with KTLS? Would someone need to explicitly disable KTLS in order to use MPTCP? Would someone need to explicitly disable MPTCP in order to use KTLS? This interaction would need to be documented on the lighttpd wiki in both places where MPTCP will be documented, and where lighttpd TLS modules with KTLS are documented.

Unfortunately, MPTCP and KTLS not being supported together is a blocker for enabling MPTCP by default in lighttpd.

To keep progress moving, I am going to accept this patch, and will follow with another commit which makes some adjustments.

@gstrauss
Copy link
Member

My commits will automatically close this PR, but I welcome continuing the conversation here or in another PR.

lighttpd-git pushed a commit that referenced this pull request Mar 18, 2024
Multipath TCP (MPTCP), standardized in RFC8684 [1], is a TCP extension
that enables a TCP connection to use different paths.
Multipath TCP has been used for several use cases.
On smartphones, Multipath TCP enables seamless handovers between
cellular and Wi-Fi networks while preserving established connections.
This use-case is what pushed Apple to use MPTCP since 2013 in multiple
applications [2]. On dual-stack hosts, Multipath TCP enables the TCP
connection to automatically use the best performing path,
either IPv4 or IPv6. If one path fails, Multipath TCP automatically
uses the other path. To benefit from MPTCP, both the client and the
server have to support it.

Multipath TCP is a backward-compatible TCP extension that is enabled by
default on recent Linux distributions (Debian, Ubuntu, Redhat, ...).
Multipath TCP is included in the Linux kernel since version 5.6 [3].
To use Multipath TCP on Linux, a server application must explicitly
enable it when creating the socket. No need to change anything else in
the application. This is what the attached patch is doing.

It is important to note that a MPTCP-enabled server continues to accept
regular TCP connections that do not use the Multipath TCP extension
without any performance impact.

When a connection request is received, and is linked to a listening
socket with MPTCP support, the kernel will simply check if MPTCP
options are present. If not, the accepted socket will be a "plain" TCP
one.

This commit modifies the creation of sockets on Linux, trying to use
Multipath TCP is available. If not, a plain TCP socket is created
instead. A new option is now available: `server.network-mptcp`. This
option is enabled by default. It seems important to enable this option
by default on the server side, to let clients using MPTCP while not
degrading performances when "plain" TCP is used. If the option is not
enabled by default, it looks difficult to convince each administrator to
enable it explicitly. This technology is mainly useful for the client
side while it doesn't change much for the server side where no other
configuration is needed.

Link: https://www.rfc-editor.org/rfc/rfc8684.html [1]
Link: https://www.tessares.net/apples-mptcp-story-so-far/ [2]
Link: https://www.mptcp.dev [3]
Co-developed-by: Maxime Dourov <mux99@live.be>
Co-developed-by: Olivier Bonaventure <Olivier.Bonaventure@uclouvain.be>
Co-developed-by: Matthieu Baerts <matttbe@kernel.org>
Signed-off-by: Maxime Dourov (UCLouvain) <mux99@live.be>

x-ref:
  "[core] add MPTCP support"
  #132
@gstrauss
Copy link
Member

even though my subsequent commit message erroneously says "omit trace", I left the trace in if socket() with IPPROTO_MPTCP fails since server.network-mptcp config is disabled by default.

@matttbe
Copy link

matttbe commented Mar 19, 2024

Future looking: lighttpd mod_openssl and lighttpd mod_gnutls call uname() and check the linux kernel version for KTLS support. Runtime feature checks could be performed at startup to test for IPPROTO_MPTCP support, which I would suggest if keeping the log_pdebug() trace is desired. (I currently do not think the trace is needed, but can be convinced.)

Interesting, that could be used indeed, but please note that even if the kernel is recent enough (v5.6), MPTCP might still not be available: not compiled (CONFIG_MPTCP), disabled via a sysctl or SELinux, etc.

lighttpd could at startup perform a single socket() with IPPROTO_MPTCP test and set a file-scoped static variable with whether or not IPPROTO_MPTCP is available, rather than checking uname() and sysctl and ...

Yes, it is better. (I don't know why I didn't think about that, that's usually what we recommend :) )

Please also note that MPTCP is currently not compatible with KTLS: they both use the same technique to extend TCP behaviour in the kernel (TCP ULP). Technically, it should be possible to chain these extensions, but that's not possible today (mainly because nobody requested it I think). I didn't know KTLS was used by default in lighttpd.

Whether or not KTLS is enabled by default in lighttpd, that MPTCP and KTLS are not supported together should be carefully documented on the MPTCP site and this info shared with other projects to which your team sends patches.

Yes, we forgot about the KTLS incompatibility, but we should and we will document this!

And we will check if it is possible to support both later on: multipath-tcp/mptcp_net-next#480

What behavior/errors would someone see if they enabled MPTCP in lighttpd and also use lighttpd mod_openssl or lighttpd mod_gnutls with KTLS? Would someone need to explicitly disable KTLS in order to use MPTCP? Would someone need to explicitly disable MPTCP in order to use KTLS? This interaction would need to be documented on the lighttpd wiki in both places where MPTCP will be documented, and where lighttpd TLS modules with KTLS are documented.

For the moment, MPTCP and KTLS cannot work together. If you create an MPTCP socket, and you want to use KTLS, the setsockopt() call (setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"));) will return an error. I'm sorry, I don't know how OpenSSL and GnuTLS handle that. Technically, it is easy to check try this setsockopt() and fall back if it is not available. We will investigate.

Unfortunately, MPTCP and KTLS not being supported together is a blocker for enabling MPTCP by default in lighttpd.

We understand.

To keep progress moving, I am going to accept this patch, and will follow with another commit which makes some adjustments.

Thank you for having accepted the patch, and for these adjustments!

@mux99
Copy link
Contributor Author

mux99 commented Mar 20, 2024

Hi, after some testing, it appears that when MPTCP is enabled at the same time as KTLS. Since they are incompatible, the setsockopt() call to enable KTLS returns an error (ENOPROTOOPT) error and the SSL/TLS lib stays in user-space.
From a user point of view, the website works fine but KTLS was quietly ignored.
To be noted: This error does not occur in the lighttpd code but in the openssl library.

@gstrauss
Copy link
Member

@mux99 thank you for confirming. Yes, openssl will recover if KTLS is not available. However, there will be a cost of one failed system call per connection to find that out. Just FYI: attempting to avoiding that excess syscall is the reason I added the uname() check in lighttpd mod_openssl at lighttpd start up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants