[nginx] Support for Multipath TCP on Linux.

Maxim Dounin mdounin at mdounin.ru
Mon Apr 7 17:57:37 UTC 2025


details:   http://freenginx.org/hg/nginx/rev/cb20978439c8
branches:  
changeset: 9337:cb20978439c8
user:      Maxim Dounin <mdounin at mdounin.ru>
date:      Mon Apr 07 18:57:35 2025 +0300
description:
Support for Multipath TCP on Linux.

With this change, Multipath TCP on Linux can be activated with
"listen ... multipath".  The "listen ... multipath" option is supported
in http, stream, and mail modules.

Note that on Linux to activate Multipath TCP one should create a socket
with the IPPROTO_MPTCP protocol explicitly specified, and it is not possible
to change an existing socket.  To make transition possible with minimal
impact on client connections, if the multipath option is changed in the
configuration, SO_REUSEPORT is set on the old socket, and the new socket
is opened with IPPROTO_MPTCP.

Note that this creates a race window, and connection requests which are
assigned to the old socket will be lost.  In particular, this might affect
binary upgrade when the WINCH signal is used to preserve the old master
process.

Requires Linux kernel 5.6 or newer.  Note though that some of the socket
options might not be supported with Multipath TCP or only supported in
new kernels.  Most notably, TCP_NODELAY is only supported with kernel
version 5.17 or newer.

Based on patches by Maxime Dourov and Anthony Doeraene.

diffstat:

 auto/unix                           |  11 ++++++++
 src/core/ngx_connection.c           |  50 ++++++++++++++++++++++++++++++++++--
 src/core/ngx_connection.h           |   2 +
 src/core/ngx_cycle.c                |   6 ++++
 src/http/ngx_http.c                 |   4 ++
 src/http/ngx_http_core_module.c     |  19 ++++++++++++++
 src/http/ngx_http_core_module.h     |   1 +
 src/mail/ngx_mail.c                 |   4 ++
 src/mail/ngx_mail.h                 |   1 +
 src/mail/ngx_mail_core_module.c     |  12 ++++++++
 src/stream/ngx_stream.c             |   4 ++
 src/stream/ngx_stream.h             |   1 +
 src/stream/ngx_stream_core_module.c |  18 +++++++++++++
 13 files changed, 129 insertions(+), 4 deletions(-)

diffs (326 lines):

diff --git a/auto/unix b/auto/unix
--- a/auto/unix
+++ b/auto/unix
@@ -532,6 +532,17 @@ ngx_feature_test="socklen_t optlen = siz
 . auto/feature
 
 
+ngx_feature="IPPROTO_MPTCP"
+ngx_feature_name="NGX_HAVE_MULTIPATH"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/socket.h>
+                  #include <netinet/in.h>"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="socket(0, 0, IPPROTO_MPTCP)"
+. auto/feature
+
+
 ngx_feature="accept4()"
 ngx_feature_name="NGX_HAVE_ACCEPT4"
 ngx_feature_run=no
diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c
--- a/src/core/ngx_connection.c
+++ b/src/core/ngx_connection.c
@@ -150,6 +150,9 @@ ngx_set_inherited_sockets(ngx_cycle_t *c
 #if (NGX_HAVE_REUSEPORT)
     int                        reuseport;
 #endif
+#if (NGX_HAVE_MULTIPATH)
+    int                        protocol;
+#endif
 
     ls = cycle->listening.elts;
     for (i = 0; i < cycle->listening.nelts; i++) {
@@ -338,6 +341,25 @@ ngx_set_inherited_sockets(ngx_cycle_t *c
 
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+
+        protocol = 0;
+        olen = sizeof(int);
+
+        if (getsockopt(ls[i].fd, SOL_SOCKET, SO_PROTOCOL,
+                       (void *) &protocol, &olen)
+            == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+                          "getsockopt(SO_PROTOCOL) %V failed, ignored",
+                          &ls[i].addr_text);
+
+        } else {
+            ls[i].multipath = (protocol == IPPROTO_MPTCP) ? 1 : 0;
+        }
+
+#endif
+
 #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
 
         ngx_memzero(&af, sizeof(struct accept_filter_arg));
@@ -406,7 +428,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *c
 ngx_int_t
 ngx_open_listening_sockets(ngx_cycle_t *cycle)
 {
-    int               reuseaddr;
+    int               reuseaddr, proto;
     ngx_uint_t        i, tries, failed;
     ngx_err_t         err;
     ngx_log_t        *log;
@@ -436,7 +458,7 @@ ngx_open_listening_sockets(ngx_cycle_t *
 
 #if (NGX_HAVE_REUSEPORT)
 
-            if (ls[i].add_reuseport) {
+            if (ls[i].add_reuseport || ls[i].reopen) {
 
                 /*
                  * to allow transition from a socket without SO_REUSEPORT
@@ -472,6 +494,18 @@ ngx_open_listening_sockets(ngx_cycle_t *
 
                 ls[i].add_reuseport = 0;
             }
+
+            if (ls[i].reopen) {
+
+                /*
+                 * to allow transition to Multipath TCP we set SO_REUSEPORT
+                 * on the old socket, and then open a new one
+                 */
+
+                ls[i].fd = (ngx_socket_t) -1;
+                ls[i].inherited = 0;
+                ls[i].previous->remain = 0;
+            }
 #endif
 
             if (ls[i].fd != (ngx_socket_t) -1) {
@@ -487,7 +521,15 @@ ngx_open_listening_sockets(ngx_cycle_t *
                 continue;
             }
 
-            s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
+            proto = 0;
+
+#if (NGX_HAVE_MULTIPATH)
+            if (ls[i].multipath) {
+                proto = IPPROTO_MPTCP;
+            }
+#endif
+
+            s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, proto);
 
             if (s == (ngx_socket_t) -1) {
                 ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
@@ -517,7 +559,7 @@ ngx_open_listening_sockets(ngx_cycle_t *
 
 #if (NGX_HAVE_REUSEPORT)
 
-            if (ls[i].reuseport && !ngx_test_config) {
+            if ((ls[i].reuseport || ls[i].reopen) && !ngx_test_config) {
                 int  reuseport;
 
                 reuseport = 1;
diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -57,6 +57,7 @@ struct ngx_listening_s {
     unsigned            open:1;
     unsigned            remain:1;
     unsigned            ignore:1;
+    unsigned            reopen:1;
 
     unsigned            bound:1;       /* already bound */
     unsigned            inherited:1;   /* inherited from previous process */
@@ -72,6 +73,7 @@ struct ngx_listening_s {
 #endif
     unsigned            reuseport:1;
     unsigned            add_reuseport:1;
+    unsigned            multipath:1;
     unsigned            keepalive:2;
     unsigned            quic:1;
 
diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c
--- a/src/core/ngx_cycle.c
+++ b/src/core/ngx_cycle.c
@@ -581,6 +581,12 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
                     }
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+                    if (ls[i].multipath != nls[n].multipath) {
+                        nls[n].reopen = 1;
+                    }
+#endif
+
                     break;
                 }
             }
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1880,6 +1880,10 @@ ngx_http_add_listening(ngx_conf_t *cf, n
     ls->reuseport = addr->opt.reuseport;
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+    ls->multipath = addr->opt.multipath;
+#endif
+
     ls->wildcard = addr->opt.wildcard;
 
 #if (NGX_HTTP_V3)
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -4179,6 +4179,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
             continue;
         }
 
+        if (ngx_strcmp(value[n].data, "multipath") == 0) {
+#if (NGX_HAVE_MULTIPATH)
+            lsopt.multipath = 1;
+            lsopt.set = 1;
+            lsopt.bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "multipath is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
         if (ngx_strcmp(value[n].data, "ssl") == 0) {
 #if (NGX_HTTP_SSL)
             lsopt.ssl = 1;
@@ -4345,6 +4358,12 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
         }
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+        if (lsopt.multipath) {
+            return "\"multipath\" parameter is incompatible with \"quic\"";
+        }
+#endif
+
 #if (NGX_HTTP_SSL)
         if (lsopt.ssl) {
             return "\"ssl\" parameter is incompatible with \"quic\"";
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -81,6 +81,7 @@ typedef struct {
 #endif
     unsigned                   deferred_accept:1;
     unsigned                   reuseport:1;
+    unsigned                   multipath:1;
     unsigned                   so_keepalive:2;
     unsigned                   proxy_protocol:1;
 
diff --git a/src/mail/ngx_mail.c b/src/mail/ngx_mail.c
--- a/src/mail/ngx_mail.c
+++ b/src/mail/ngx_mail.c
@@ -347,6 +347,10 @@ ngx_mail_optimize_servers(ngx_conf_t *cf
             ls->ipv6only = addr[i].opt.ipv6only;
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+            ls->multipath = addr[i].opt.multipath;
+#endif
+
             mport = ngx_palloc(cf->pool, sizeof(ngx_mail_port_t));
             if (mport == NULL) {
                 return NGX_CONF_ERROR;
diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -40,6 +40,7 @@ typedef struct {
 #if (NGX_HAVE_INET6)
     unsigned                ipv6only:1;
 #endif
+    unsigned                multipath:1;
     unsigned                so_keepalive:2;
     unsigned                proxy_protocol:1;
 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c
--- a/src/mail/ngx_mail_core_module.c
+++ b/src/mail/ngx_mail_core_module.c
@@ -456,6 +456,18 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx
 #endif
         }
 
+        if (ngx_strcmp(value[i].data, "multipath") == 0) {
+#if (NGX_HAVE_MULTIPATH)
+            ls->multipath = 1;
+            ls->bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "multipath is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
         if (ngx_strcmp(value[i].data, "ssl") == 0) {
 #if (NGX_MAIL_SSL)
             ngx_mail_ssl_conf_t  *sslcf;
diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c
--- a/src/stream/ngx_stream.c
+++ b/src/stream/ngx_stream.c
@@ -518,6 +518,10 @@ ngx_stream_optimize_servers(ngx_conf_t *
             ls->reuseport = addr[i].opt.reuseport;
 #endif
 
+#if (NGX_HAVE_MULTIPATH)
+            ls->multipath = addr[i].opt.multipath;
+#endif
+
             stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
             if (stport == NULL) {
                 return NGX_CONF_ERROR;
diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h
--- a/src/stream/ngx_stream.h
+++ b/src/stream/ngx_stream.h
@@ -55,6 +55,7 @@ typedef struct {
     unsigned                       ipv6only:1;
 #endif
     unsigned                       reuseport:1;
+    unsigned                       multipath:1;
     unsigned                       so_keepalive:2;
     unsigned                       proxy_protocol:1;
 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c
--- a/src/stream/ngx_stream_core_module.c
+++ b/src/stream/ngx_stream_core_module.c
@@ -738,6 +738,18 @@ ngx_stream_core_listen(ngx_conf_t *cf, n
             continue;
         }
 
+        if (ngx_strcmp(value[i].data, "multipath") == 0) {
+#if (NGX_HAVE_MULTIPATH)
+            ls->multipath = 1;
+            ls->bind = 1;
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "multipath is not supported "
+                               "on this platform, ignored");
+#endif
+            continue;
+        }
+
         if (ngx_strcmp(value[i].data, "ssl") == 0) {
 #if (NGX_STREAM_SSL)
             ngx_stream_ssl_conf_t  *sslcf;
@@ -884,6 +896,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, n
             return "\"fastopen\" parameter is incompatible with \"udp\"";
         }
 #endif
+
+#if (NGX_HAVE_MULTIPATH)
+        if (ls->multipath) {
+            return "\"multipath\" parameter is incompatible with \"udp\"";
+        }
+#endif
     }
 
     for (n = 0; n < u.naddrs; n++) {


More information about the nginx-devel mailing list